diff --git a/.dev/vagrant/motd.txt b/.dev/vagrant/motd.txt index 823172e45..22089d55a 100644 --- a/.dev/vagrant/motd.txt +++ b/.dev/vagrant/motd.txt @@ -13,5 +13,5 @@ Default panel users: MySQL is accessible using root/pterodactyl or pterodactyl/pterodactyl -Services for pteroq and mailhog are running +Service for pteroq and mailhog are running ##################################################### diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh index f5a35a0f2..41b3fa920 100644 --- a/.dev/vagrant/provision.sh +++ b/.dev/vagrant/provision.sh @@ -65,8 +65,8 @@ composer install --no-progress php artisan key:generate --force php artisan migrate php artisan db:seed -php artisan pterodactyl:user --firstname Test --lastname Admin --username admin --email testadmin@pterodactyl.io --password Ptero123 --admin 1 -php artisan pterodactyl:user --firstname Test --lastname User --username user --email testuser@pterodactyl.io --password Ptero123 --admin 0 +php artisan p:user:make --name-first Test --name-last Admin --username admin --email testadmin@pterodactyl.io --password Ptero123 --admin 1 +php artisan p:user:make --name-first Test --name-last User --username user --email testuser@pterodactyl.io --password Ptero123 --admin 0 echo "Add queue cronjob and start queue worker" (crontab -l 2>/dev/null; echo "* * * * * php /var/www/html/pterodactyl/artisan schedule:run >> /dev/null 2>&1") | crontab - diff --git a/.env.example b/.env.example index fa7e20965..e6eba87e0 100644 --- a/.env.example +++ b/.env.example @@ -1,38 +1,29 @@ APP_ENV=production APP_DEBUG=false -APP_KEY=SomeRandomString3232RandomString +APP_KEY= APP_THEME=pterodactyl -APP_TIMEZONE=UTC +APP_TIMEZONE=America/New_York APP_CLEAR_TASKLOG=720 APP_DELETE_MINUTES=10 -APP_URL=http://yoursite.com/ +APP_ENVIRONMENT_ONLY=true -DB_HOST=localhost +DB_HOST=127.0.0.1 DB_PORT=3306 -DB_DATABASE=homestead -DB_USERNAME=homestead -DB_PASSWORD=secret +DB_DATABASE=panel +DB_USERNAME=pterodactyl +DB_PASSWORD= -CACHE_DRIVER=file -SESSION_DRIVER=database +HASHIDS_SALT= +HASHIDS_LENGTH=8 MAIL_DRIVER=smtp MAIL_HOST=mailtrap.io MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=null -MAIL_FROM=you@example.com +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_ENCRYPTION=tls +MAIL_FROM=no-reply@example.com -API_PREFIX=api -API_VERSION=v1 -API_DEBUG=false - -QUEUE_DRIVER=database QUEUE_HIGH=high QUEUE_STANDARD=standard QUEUE_LOW=low - -SQS_KEY=aws-public -SQS_SECRET=aws-secret -SQS_QUEUE_PREFIX=aws-queue-prefix diff --git a/.env.travis b/.env.travis new file mode 100644 index 000000000..f1d4f5698 --- /dev/null +++ b/.env.travis @@ -0,0 +1,19 @@ +APP_ENV=testing +APP_DEBUG=true +APP_KEY=SomeRandomString3232RandomString +APP_THEME=pterodactyl +APP_TIMEZONE=UTC +APP_URL=http://localhost/ + +DB_HOST=127.0.0.1 +DB_DATABASE=travis +DB_USERNAME=root +DB_PASSWORD="" + +CACHE_DRIVER=array +SESSION_DRIVER=array +MAIL_DRIVER=array +QUEUE_DRIVER=sync + +HASHIDS_SALT=test123 +APP_ENVIRONMENT_ONLY=true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 346d32290..2f3c18c72 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,3 +4,11 @@ If you're just making a suggestion, be descriptive, and link to any issues that You can delete from this line up. --------------------- + +* Panel or Daemon: +* Version of Panel/Daemon: +* Server's OS: +* Your Computer's OS & Browser: + +## Add Details Below: + diff --git a/.gitignore b/.gitignore index 9d8eca6a5..2422de18a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ .vscode/* storage/framework/* /.idea +/nbproject +package-lock.json composer.lock node_modules @@ -23,3 +25,4 @@ docker-compose.yml # for image related files misc .phpstorm.meta.php +.php_cs.cache diff --git a/.php_cs b/.php_cs index 791899b72..c854af47c 100644 --- a/.php_cs +++ b/.php_cs @@ -1,7 +1,55 @@ in([ + 'app', + 'bootstrap', + 'config', + 'database', + 'resources/lang', + 'routes', + 'tests', + ]); -use SLLH\StyleCIBridge\ConfigBridge; - -return ConfigBridge::create(); +return PhpCsFixer\Config::create() + ->setRules([ + '@Symfony' => true, + '@PSR1' => true, + '@PSR2' => true, + 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_before_return' => true, + 'blank_line_before_statement' => false, + 'combine_consecutive_unsets' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'single'], + 'heredoc_to_nowdoc' => true, + 'increment_style' => ['style' => 'post'], + 'linebreak_after_opening_tag' => true, + 'method_argument_space' => [ + 'ensure_fully_multiline' => false, + 'keep_multiple_spaces_after_comma' => false, + ], + 'new_with_braces' => false, + 'no_alias_functions' => true, + 'no_multiline_whitespace_before_semicolons' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_return' => true, + 'not_operator_with_successor_space' => true, + 'ordered_imports' => [ + 'sortAlgorithm' => 'length', + ], + 'phpdoc_align' => ['tags' => ['param']], + 'phpdoc_separation' => false, + 'protected_to_private' => false, + 'psr0' => ['dir' => 'app'], + 'psr4' => true, + 'random_api_migration' => true, + 'standardize_not_equals' => true, + 'ternary_to_null_coalescing' => true, + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], + ])->setRiskyAllowed(true)->setFinder($finder); diff --git a/.styleci.yml b/.styleci.yml index 7595f5546..87848d020 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -4,3 +4,4 @@ disabled: - concat_without_spaces enabled: - concat_with_spaces + - no_unused_imports diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..0a19b3748 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: php +dist: trusty +php: + - 7.2 +sudo: false +cache: + directories: + - $HOME/.composer/cache +services: + - mysql +before_install: + - mysql -e 'CREATE DATABASE IF NOT EXISTS travis;' +before_script: + - echo 'opcache.enable_cli=1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - cp .env.travis .env + - travis_retry composer install --no-interaction --prefer-dist --no-suggest + - php artisan migrate --seed +script: + - vendor/bin/phpunit --coverage-clover coverage.xml +notifications: + email: false + webhooks: + urls: + - https://misc.schrej.net/travistodiscord/pterodev.php + on_success: change + on_failure: always + on_error: always + on_cancel: always + on_start: never +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3dacb3e7..c4e391c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,209 @@ 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. +## v0.7.0 (Derelict Dermodactylus) +### Fixed +* `[rc.2]` — Fixes bad API behavior on `/user` routes. +* `[rc.2]` — Fixes Admin CP user editing resetting a password on users unintentionally. +* `[rc.2]` — Fixes bug with server creation API endpoint that would fail to validate `allocation.default` correctly. +* `[rc.2]` — Fix data integrity exception occuring due to invalid data being passed to server creation service on the API. +* `[rc.2]` — Fix data integrity exception that could occur when an email containing non-username characters was passed. +* `[rc.2]` — Fix data integrity exception occurring when no default value is provided for an egg variable. +* `[rc.2]` — Fixes a bug that would cause non-editable variables on the front-end to throw a validation error. +* `[rc.2]` — Fixes a data integrity exception occurring when saving egg variables with no value. +* Fixes a design bug in the database that prevented the storage of negative numbers, thus preventing a server from being assigned unlimited swap. +* Fixes a bug where the 'Assign New Allocations' box would only show IPs that were present in the current pagination block. +* Unable to change the daemon secret for a server via the Admin CP. +* Using default value in rules when creating a new variable if the rules is empty. +* Fixes a design-flaw in the allocation management part of nodes that would run a MySQL query for each port being allocated. This behavior is now changed to only execute one query to add multiple ports at once. +* Attempting to create a server when no nodes are configured now redirects to the node creation page. +* Fixes missing library issue for teamspeak when used with mariadb. +* Fixes inability to change the default port on front-end when viewing a server. +* Fixes bug preventing deletion of nests that have other nests referencing them as children. +* Fixes console sometimes not loading properly on slow connections + +### Added +* Added ability to search the following API endpoints: list users, list servers, and list locations. +* Add support for finding a user by external ID using `/api/application/users/external/` or by passing it as the search term when listing all users. +* Added a unique key to the servers table to data integrity issues where an allocation would be assigned to more than one server at once. +* Added support for editing an existing schedule. +* Added support for editing symlinked files on the Panel. +* Added new application specific API to Panel with endpoints at `/api/application`. Includes new Admin CP interface for managing keys and an easier permissions system. +* Nest and Egg listings now show the associated ID in order to make API requests easier. +* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin. +* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well. +* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. +* File manager now supports mass deletion option for files and folders. +* Support for CS:GO as a default service option selection. +* Support for GMOD as a default service option selection. +* Added test suite for core aspects of the project (Services, Repositories, Commands, etc.) to lessen the chances for bugs to escape into releases. +* New CLI command to disabled 2-Factor Authentication on an account if necessary. +* Ability to delete users and locations via the CLI. +* You can now require 2FA for all users, admins only, or at will using a simple configuration in the Admin CP. +* **Added ability to export and import service options and their associated settings and environment variables via the Admin CP.** +* Default allocation for a server can be changed on the front-end by users. This includes two new subuser permissions as well. +* Significant improvements to environment variable control for servers. Now ships with built-in abilities to define extra variables in the Panel's configuration file, or in-code for those heavily modifying the Panel. +* Quick link to server edit view in ACP on frontend when viewing servers. +* Databases created in the Panel now include `EXECUTE` privilege. + +### Changed +* PHP 7.2 is now the minimum required version for this software. +* Egg variable default values are no longer validated aganist the ruleset when configuring them. Validation of those rules will only occur when editing or creating a server. +* Changed logger to skip reporting stack-traces on PDO exceptions due to sensitive information being contained within. +* Changed behavior of allocation IP Address/Ports box to automatically store the value entered if a user unfocuses the field without hitting space. +* Changed order in which allocations are displayed to prioritize those with servers attached (in ascending IP & port order) followed by ascending IP & port order where no server is attached. +* Revoking the administrative status for an admin will revoke all authentication tokens currently assigned to their account. +* Updated core framework to Laravel 5.5. This includes many dependency updates. +* Certain AWS specific environment keys were changed, this should have minimal impact on users unless you specifically enabled AWS specific features. The renames are: `AWS_KEY -> AWS_ACCESS_KEY_ID`, `AWS_SECRET -> AWS_SECRET_ACCESS_KEY`, `AWS_REGION -> AWS_DEFAULT_REGION` +* API keys have been changed to only use a single public key passed in a bearer token. All existing keys can continue being used, however only the first 32 characters should be sent. +* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected. +* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database. +* Login page UI has been improved to be more sleek and welcoming to users. +* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials. +* **Services renamed to Nests. Service Options renamed to Eggs.** 🥚 +* Theme colors and login pages updated to give a more unique feel to the project. +* Massive overhaul to the backend code that allows for much easier updating of core functionality as well as support for better testing. This overhaul also reduces complex code logic, and allows for faster response times in the application. +* CLI commands updated to be easier to type, now stored in the `p:` namespace. +* Logout icon is now more universal and not just a power icon. +* Administrative logout notice now uses SWAL rather than a generic javascript popup. +* Server creation page now only asks for a node to deploy to, rather than requiring a location and then a node. +* Database passwords are now hidden by default and will only show if clicked on. In addition, database view in ACP now indicates that passwords must be viewed on the front-end. +* Localhost cannot be used as a connection address in the environment configuration script. `127.0.0.1` is allowed. +* Application locale can now be quickly set using an environment variable `APP_LOCALE` rather than having to edit core files. + +### Removed +* OOM exceptions can no longer be disabled on servers due to a startling number of users that were using it to avoid allocating proper amounts of resources to servers. +* SFTP settings page now only displays connection address and username. Password setting was removed as it is no longer necessary with Daemon changes. + +## v0.7.0-rc.2 (Derelict Dermodactylus) +### Fixed +* `[rc.1]` — Fixes exception thrown when revoking user sessions. +* `[rc.1]` — Fixes exception that would occur when trying to delete allocations from a node. +* `[rc.1]` — Fixes exception thown when attempting to adjust mail settings as well as a validation error thrown afterwards. +* `[rc.1]` — Fixes bug preventing modification of the default value for an Egg variable. +* `[rc.1]` — Fixed a bug that would occur when attempting to reset the daemon secret for a node. +* `[rc.1]` — Fix exception thrown when attempting to modify an existing database host. +* `[rc.1]` — Fix an auto deployment bug causing a node to be ignored if it had no servers already attached to it. + +### Changed +* Changed logger to skip reporting stack-traces on PDO exceptions due to sensitive information being contained within. + +### Added +* Added support for editing an existing schedule. + +## v0.7.0-rc.1 (Derelict Dermodactylus) +### Fixed +* `[beta.4]` — Fixes some bad search and replace action that happened previously and was throwing errors when validating user permissions. +* `[beta.4]` — Fixes behavior of variable validation to not break the page when no rules are provided. +* `[beta.4]` — Fix bug preventing the editing of files in the file manager. + +### Added +* Added support for editing symlinked files on the Panel. +* Added new application specific API to Panel with endpoints at `/api/application`. Includes new Admin CP interface for managing keys and an easier permissions system. + +## v0.7.0-beta.4 (Derelict Dermodactylus) +### Fixed +* `[beta.3]` — Fixes a bug with the default environment file that was causing an inability to perform a fresh install when running package discovery. +* `[beta.3]` — Fixes an edge case caused by the Laravel 5.5 upgrade that would try to perform an in_array check aganist a null value. +* `[beta.3]` — Fixes a bug that would cause an error when attempting to create a new user on the Panel. +* `[beta.3]` — Fixes error handling of the settings service provider when no migrations have been run. +* `[beta.3]` — Fixes validation error when trying to use 'None' as the 'Copy Script From' option for an egg script. +* Fixes a design bug in the database that prevented the storage of negative numbers, thus preventing a server from being assigned unlimited swap. +* Fixes a bug where the 'Assign New Allocations' box would only show IPs that were present in the current pagination block. + +### Added +* Nest and Egg listings now show the associated ID in order to make API requests easier. + +### Changed +* Changed behavior of allocation IP Address/Ports box to automatically store the value entered if a user unfocuses the field without hitting space. +* Changed order in which allocations are displayed to prioritize those with servers attached (in ascending IP & port order) followed by ascending IP & port order where no server is attached. + +### Removed +* OOM exceptions can no longer be disabled on servers due to a startling number of users that were using it to avoid allocating proper amounts of resources to servers. + +## v0.7.0-beta.3 (Derelict Dermodactylus) +### Fixed +* `[beta.2]` — Fixes a bug that would cause an endless exception message stream in the console when attemping to setup environment settings in certain instances. +* `[beta.2]` — Fixes a bug causing the dropdown menu for a server's egg to display the wrong selected value. +* `[beta.2]` — Fixes a bug that would throw a red page of death when submitting an invalid egg variable value for a server in the Admin CP. +* `[beta.2]` — Someone found a `@todo` that I never `@todid` and thus database hosts could not be created without being linked to a node. This is fixed... +* `[beta.2]` — Fixes bug that caused incorrect rendering of CPU usage on server graphs due to missing variable. +* `[beta.2]` — Fixes bug causing schedules to be un-deletable. +* `[beta.2]` — Fixes bug that prevented the deletion of nodes due to an allocation deletion cascade issue with the SQL schema. +* `[beta.2]` — Fixes a bug causing eggs not extending other eggs to fail validation. + +### Changed +* Revoking the administrative status for an admin will revoke all authentication tokens currently assigned to their account. +* Updated core framework to Laravel 5.5. This includes many dependency updates. +* Certain AWS specific environment keys were changed, this should have minimal impact on users unless you specifically enabled AWS specific features. The renames are: `AWS_KEY -> AWS_ACCESS_KEY_ID`, `AWS_SECRET -> AWS_SECRET_ACCESS_KEY`, `AWS_REGION -> AWS_DEFAULT_REGION` +* API keys have been changed to only use a single public key passed in a bearer token. All existing keys can continue being used, however only the first 32 characters should be sent. + +### Added +* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin. +* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well. + +## v0.7.0-beta.2 (Derelict Dermodactylus) +### Fixed +* `[beta.1]` — Fixes a CORS header issue due to a wrong API endpoint being provided in the administrative node listing. +* `[beta.1]` — Fixes bug that would prevent root admins from accessing servers they were not set as the owner of. +* `[beta.1]` — Fixes wrong URL redirect being provided when creating a subuser. +* `[beta.1]` — Fixes missing check in environment setup that would leave the Hashids salt empty. +* `[beta.1]` — Fixes bug preventing loading of allocations when trying to create a new server. +* `[beta.1]` — Fixes bug causing inability to create new servers on the Panel. +* `[beta.1]` — Fixes bug causing inability to delete an allocation due to misconfigured JS. +* `[beta.1]` — Fixes bug causing inability to set the IP alias for an allocation to an empty value. +* `[beta.1]` — Fixes bug that caused startup changes to not propigate to the server correctly on the first save. +* `[beta.1]` — Fixes bug that prevented subusers from accessing anything over socketio due to a missing permission. + +### Changed +* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected. +* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database. +* Login page UI has been improved to be more sleek and welcoming to users. +* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials. + +### Added +* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. + +## v0.7.0-beta.1 (Derelict Dermodactylus) +### Added +* File manager now supports mass deletion option for files and folders. +* Support for CS:GO as a default service option selection. +* Support for GMOD as a default service option selection. +* Added test suite for core aspects of the project (Services, Repositories, Commands, etc.) to lessen the chances for bugs to escape into releases. +* New CLI command to disabled 2-Factor Authentication on an account if necessary. +* Ability to delete users and locations via the CLI. +* You can now require 2FA for all users, admins only, or at will using a simple configuration in the Admin CP. +* **Added ability to export and import service options and their associated settings and environment variables via the Admin CP.** +* Default allocation for a server can be changed on the front-end by users. This includes two new subuser permissions as well. +* Significant improvements to environment variable control for servers. Now ships with built-in abilities to define extra variables in the Panel's configuration file, or in-code for those heavily modifying the Panel. +* Quick link to server edit view in ACP on frontend when viewing servers. +* Databases created in the Panel now include `EXECUTE` privilege. + +### Changed +* **Services renamed to Nests. Service Options renamed to Eggs.** 🥚 +* Theme colors and login pages updated to give a more unique feel to the project. +* Massive overhaul to the backend code that allows for much easier updating of core functionality as well as support for better testing. This overhaul also reduces complex code logic, and allows for faster response times in the application. +* CLI commands updated to be easier to type, now stored in the `p:` namespace. +* Logout icon is now more universal and not just a power icon. +* Administrative logout notice now uses SWAL rather than a generic javascript popup. +* Server creation page now only asks for a node to deploy to, rather than requiring a location and then a node. +* Database passwords are now hidden by default and will only show if clicked on. In addition, database view in ACP now indicates that passwords must be viewed on the front-end. +* Localhost cannot be used as a connection address in the environment configuration script. `127.0.0.1` is allowed. +* Application locale can now be quickly set using an environment variable `APP_LOCALE` rather than having to edit core files. + +### Fixed +* Unable to change the daemon secret for a server via the Admin CP. +* Using default value in rules when creating a new variable if the rules is empty. +* Fixes a design-flaw in the allocation management part of nodes that would run a MySQL query for each port being allocated. This behavior is now changed to only execute one query to add multiple ports at once. +* Attempting to create a server when no nodes are configured now redirects to the node creation page. +* Fixes missing library issue for teamspeak when used with mariadb. +* Fixes inability to change the default port on front-end when viewing a server. +* Fixes bug preventing deletion of nests that have other nests referencing them as children. +* Fixes console sometimes not loading properly on slow connections + +### Removed +* SFTP settings page now only displays connection address and username. Password setting was removed as it is no longer necessary with Daemon changes. + ## v0.6.4 (Courageous Carniadactylus) ### Fixed * Fixed the console rendering on page load, I guess people don't like watching it load line-by-line for 10 minutes. Who would have guessed... @@ -240,7 +443,7 @@ spatie/laravel-fractal (4.0.0 => 4.0.1) * New theme applied to Admin CP. Many graphical changes were made, some data was moved around and some display data changed. Too much was changed to feasibly log it all in here. Major breaking changes or notable new features will be logged. * New server creation page now makes significantly less AJAX calls and is much quicker to respond. * Server and Node view pages wee modified to split tabs into individual pages to make re-themeing and modifications significantly easier, and reduce MySQL query loads on page. -* `[pre.4]` — Services and Pack magement overhauled to be faster, cleaner, and more extensible in the future. +* `[pre.4]` — Service and Pack magement overhauled to be faster, cleaner, and more extensible in the future. * Most of the backend `UnhandledException` display errors now include a clearer error that directs admins to the program's logs. * Table seeders for services now can be run during upgrades and will attempt to locate and update, or create new if not found in the database. * Many structural changes to the database and `Pterodactyl\Models` classes that would flood this changelog if they were all included. All required migrations included to handle database changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 417a5364f..5f2512847 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,35 @@ # Contributing We're glad you want to help us out and make this panel the best that it can be! We have a few simple things to follow when making changes to files and adding new features. +### Project Branches +This section mainly applies to those with read/write access to our repositories, but can be helpful for others. + +The `develop` branch should always be in a runnable state, and not contain any major breaking features. For the most part this means you will need to create `feature/` branches in order to add new functionality, or change how things work. When making a feature branch, if it is referencing something in the issue tracker, please title the branch `feature/PTDL-###` where `###` is the issue number. + +Moving forward all commits from contributors should be in the form of a PR, unless it is something we have previous discussed as being able to be pushed right into `develop`. + +All new code should contain unit tests at minimum (where applicable). There is a lot of un-covered code currently, so as you are doing things please be looking for places that you can write tests. + +### Update the CHANGELOG +When adding something that is new, fixed, changed, or security related for the next release you should be adding a note to the CHANGELOG. If something is changing within the same version (i.e. fixing a bug introduced but not released) it should _not_ go into the CHANGELOG. + ### Code Guidelines -*This section is still under construction.* +We are a `PSR-4` and `PSR-0` compliant project, so please follow those guidelines at a minimum. In addition, StyleCI runs on all of our code to ensure the formatting is standardized across everything. When a PR is made StyleCI will analyze your code and make a pull to that branch if necessary to fix any formatting issues. This project also ships with a PHP-CS configuration file and you are welcome to configure your local environment to make use of that. -We are a `PSR-4` and `PSR-0` compliant project, so please follow those guidelines at a minimum. In addition, StyleCI runs on all of our code to ensure the formatting is standardized across everything. Please follow the existing code formatting, I will write up more detailed documentation at a later time. +All class variable declarations should be in alphabetical order, and constructor arguments should be in alphabetical order based on the classname. See the example below for how this should look, or check out any of the `app/Service` files for examples. -In addition, all functions must be properly Doc-Block'd. +```php +class ProcessScheduleService +{ + protected $repository; + protected $runnerService; + + public function __construct(RunTaskService $runnerService, ScheduleRepositoryInterface $repository) + { + $this->repository = $repository; + $this->runnerService = $runnerService; + } +``` ### Responsible Disclosure This is a fairly in-depth project, and makes use of a lot of parts. We strive to keep everything as secure as possible, and welcome you to take a look into it yourself. We do ask that you be considerate of others who are using the software and not publicly disclose security issues without contacting us first by email. @@ -18,4 +41,4 @@ If you've found what you believe is a security issue please email us at `support ### Where to find Us You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a bug or other problem open an issue on here for us to take a look at it. We also accept feature requests here as well. -You can also find us on [Discord](https://pterodactyl.io/discord) or our [community forums](https://forums.pterodactyl.io/). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our forums or here. +You can also find us on [Discord](https://pterodactyl.io/discord). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our forums or Discord. diff --git a/README.md b/README.md index 393dff90c..c74fa7945 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Logo Image](https://cdn.pterodactyl.io/logos/Banner%20Logo%20Black@2x.png)](https://pterodactyl.io) +[![Build Status](https://travis-ci.org/Pterodactyl/Panel.svg?branch=develop)](https://travis-ci.org/Pterodactyl/Panel) [![StyleCI](https://styleci.io/repos/47508644/shield?branch=develop)](https://styleci.io/repos/47508644) [![codecov](https://codecov.io/gh/Pterodactyl/Panel/branch/develop/graph/badge.svg)](https://codecov.io/gh/Pterodactyl/Panel) + ## Pterodactyl Panel Pterodactyl Panel is the free, open-source, game agnostic, self-hosted control panel for users, networks, and game service providers. Pterodactyl supports games and servers such as Minecraft (including Spigot, Bungeecord, and Sponge), ARK: Evolution Evolved, CS:GO, Team Fortress 2, Insurgency, Teamspeak 3, Mumble, and many more. Control all of your games from one unified interface. @@ -33,47 +35,43 @@ SOFTWARE. ![](http://static.s3.pterodactyl.io/PhraseApp-parrot.png) A huge thanks to [PhraseApp](https://phraseapp.com) who provide us the software to help translate this project. -Ace Editor - [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) - [homepage](https://ace.c9.io) +Ace Editor — [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) — [homepage](https://ace.c9.io) -AdminLTE - [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICENSE) - [homepage](https://almsaeedstudio.com) +AdminLTE — [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICENSE) — [homepage](https://almsaeedstudio.com) -Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/) +Animate.css — [license](https://github.com/daneden/animate.css/blob/master/LICENSE) — [homepage](http://daneden.github.io/animate.css/) -AnsiUp - [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) - [homepage](https://github.com/drudru/ansi_up) +AnsiUp — [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) — [homepage](https://github.com/drudru/ansi_up) -Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) +Async.js — [license](https://github.com/caolan/async/blob/master/LICENSE) — [homepage](https://github.com/caolan/async/) -Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com) +Bootstrap — [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) — [homepage](http://getbootstrap.com) -BootStrap Notify - [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) - [homepage](http://bootstrap-notify.remabledesigns.com) +BootStrap Notify — [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) — [homepage](http://bootstrap-notify.remabledesigns.com) -Chart.js - [license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md) - [homepage](http://www.chartjs.org) +Chart.js — [license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md) — [homepage](http://www.chartjs.org) -FontAwesome - [license](http://fontawesome.io/license/) - [homepage](http://fontawesome.io) +FontAwesome — [license](http://fontawesome.io/license/) — [homepage](http://fontawesome.io) -FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation) +FontAwesome Animations — [license](https://github.com/l-lin/font-awesome-animation#license) — [homepage](https://github.com/l-lin/font-awesome-animation) -jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com) +jQuery — [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) — [homepage](http://jquery.com) -Laravel Framework - [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) - [homepage](https://laravel.com) +Laravel Framework — [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) — [homepage](https://laravel.com) -Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/) +Lodash — [license](https://github.com/lodash/lodash/blob/master/LICENSE) — [homepage](https://lodash.com/) -Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io) +Select2 — [license](https://github.com/select2/select2/blob/master/LICENSE.md) — [homepage](https://select2.github.io) -Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io) +Socket.io — [license](https://github.com/socketio/socket.io/blob/master/LICENSE) — [homepage](http://socket.io) -Socket.io File Upload - [license](https://github.com/vote539/socketio-file-upload/blob/master/server.js#L1-L27) - [homepage](https://github.com/vote539/socketio-file-upload) +Socket.io File Upload — [license](https://github.com/vote539/socketio-file-upload/blob/master/server.js#L1-L27) — [homepage](https://github.com/vote539/socketio-file-upload) -SweetAlert - [license](https://github.com/t4t5/sweetalert/blob/master/LICENSE) - [homepage](http://t4t5.github.io/sweetalert/) +SweetAlert — [license](https://github.com/t4t5/sweetalert/blob/master/LICENSE) — [homepage](http://t4t5.github.io/sweetalert/) Typeahead — [license](https://github.com/bassjobsen/Bootstrap-3-Typeahead/blob/master/bootstrap3-typeahead.js) — [homepage](https://github.com/bassjobsen/Bootstrap-3-Typeahead) +particles.js — [license](https://github.com/VincentGarreau/particles.js/blob/master/LICENSE.md) — [homepage](http://vincentgarreau.com/particles.js/) + ### Additional License Information Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apache 2.0`. Please check their respective header files for more information. - -Some images used within Pterodactyl are Copyright (c) their respective owners. - -`/public/themes/default/images/403.jpg` is licensed under a [CC BY 2.0](http://creativecommons.org/licenses/by/2.0/) by [BigTallGuy](http://flickr.com/photos/bigtallguy/) - -`/public/themes/default/images/404.jpg` is licensed under a [CC BY-SA 2.0](http://creativecommons.org/licenses/by-sa/2.0/) by [nicsuzor](http://flickr.com/photos/nicsuzor/) diff --git a/app/Console/Commands/AddLocation.php b/app/Console/Commands/AddLocation.php deleted file mode 100644 index d9da92466..000000000 --- a/app/Console/Commands/AddLocation.php +++ /dev/null @@ -1,75 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Illuminate\Console\Command; -use Pterodactyl\Repositories\LocationRepository; - -class AddLocation extends Command -{ - protected $data = []; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:location - {--short= : The shortcode name of this location (ex. us1).} - {--long= : A longer description of this location.}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Creates a new location on the system via the CLI.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $this->data['short'] = (is_null($this->option('short'))) ? $this->ask('Location Short Code') : $this->option('short'); - $this->data['long'] = (is_null($this->option('long'))) ? $this->ask('Location Description') : $this->option('long'); - - $repo = new LocationRepository; - $id = $repo->create($this->data); - - $this->info('Location ' . $this->data['short'] . ' created with ID: ' . $id); - } -} diff --git a/app/Console/Commands/AddNode.php b/app/Console/Commands/AddNode.php deleted file mode 100644 index 0aac540c0..000000000 --- a/app/Console/Commands/AddNode.php +++ /dev/null @@ -1,112 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Illuminate\Console\Command; -use Pterodactyl\Models\Location; -use Pterodactyl\Repositories\NodeRepository; - -class AddNode extends Command -{ - protected $data = []; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:node - {--name= : Name of the node.} - {--location= : The shortcode of the location to add this node to.} - {--fqdn= : The fully-qualified domain for the node.} - {--ssl= : Should the daemon use SSL for connections (T/F).} - {--memory= : The total memory available on this node for servers.} - {--disk= : The total disk space available on this node for servers.} - {--daemonBase= : The directory in which server files will be stored.} - {--daemonListen= : The port the daemon will listen on for connections.} - {--daemonSFTP= : The port to be used for SFTP conncetions to the daemon.}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Adds a new node to the system via the CLI.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $locations = Location::all(['id', 'short', 'long']); - - $this->data['name'] = (is_null($this->option('name'))) ? $this->ask('Node Name') : $this->option('name'); - - if (is_null($this->option('location'))) { - $this->table(['ID', 'Short Code', 'Description'], $locations->toArray()); - $selectedLocation = $this->anticipate('Node Location (Short Name)', $locations->pluck('short')->toArray()); - } else { - $selectedLocation = $this->option('location'); - } - - $this->data['location_id'] = $locations->where('short', $selectedLocation)->first()->id; - - if (is_null($this->option('fqdn'))) { - $this->line('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.'); - $this->data['fqdn'] = $this->ask('Fully Qualified Domain Name'); - } else { - $this->data['fqdn'] = $this->option('fqdn'); - } - - $useSSL = (is_null($this->option('ssl'))) ? $this->confirm('Use SSL', true) : $this->option('ssl'); - - $this->data['scheme'] = ($useSSL) ? 'https' : 'http'; - $this->data['memory'] = (is_null($this->option('memory'))) ? $this->ask('Total Memory (in MB)') : $this->option('memory'); - $this->data['memory_overallocate'] = 0; - $this->data['disk'] = (is_null($this->option('disk'))) ? $this->ask('Total Disk Space (in MB)') : $this->option('disk'); - $this->data['disk_overallocate'] = 0; - $this->data['public'] = 1; - $this->data['daemonBase'] = (is_null($this->option('daemonBase'))) ? $this->ask('Daemon Server File Location', '/srv/daemon-data') : $this->option('daemonBase'); - $this->data['daemonListen'] = (is_null($this->option('daemonListen'))) ? $this->ask('Daemon Listening Port', 8080) : $this->option('daemonListen'); - $this->data['daemonSFTP'] = (is_null($this->option('daemonSFTP'))) ? $this->ask('Daemon SFTP Port', 2022) : $this->option('daemonSFTP'); - - $repo = new NodeRepository; - $id = $repo->create($this->data); - - $this->info('Node created with ID: ' . $id); - } -} diff --git a/app/Console/Commands/CleanServiceBackup.php b/app/Console/Commands/CleanServiceBackup.php deleted file mode 100644 index 0a6a3e272..000000000 --- a/app/Console/Commands/CleanServiceBackup.php +++ /dev/null @@ -1,74 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Carbon; -use Storage; -use Illuminate\Console\Command; - -class CleanServiceBackup extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:cleanservices'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Cleans .bak files assocaited with service backups whene editing files through the panel.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $files = Storage::files('services/.bak'); - - foreach ($files as $file) { - $lastModified = Carbon::createFromTimestamp(Storage::lastModified($file)); - if ($lastModified->diffInMinutes(Carbon::now()) > 5) { - $this->info('Deleting ' . $file); - Storage::delete($file); - } - } - } -} diff --git a/app/Console/Commands/ClearServices.php b/app/Console/Commands/ClearServices.php deleted file mode 100644 index db62d268c..000000000 --- a/app/Console/Commands/ClearServices.php +++ /dev/null @@ -1,89 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use DB; -use Illuminate\Console\Command; - -class ClearServices extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:clear-services'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Removes all services from the database for installing updated ones as needed.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - if (! $this->confirm('This is a destructive operation, are you sure you wish to continue?')) { - $this->error('Canceling.'); - exit(); - } - - $bar = $this->output->createProgressBar(3); - DB::beginTransaction(); - - try { - DB::table('services')->truncate(); - $bar->advance(); - - DB::table('service_options')->truncate(); - $bar->advance(); - - DB::table('service_variables')->truncate(); - $bar->advance(); - - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - } - - $this->info("\n"); - $this->info('All services have been removed. Consider running `php artisan pterodactyl:service-defaults` at this time.'); - } -} diff --git a/app/Console/Commands/ClearTasks.php b/app/Console/Commands/ClearTasks.php deleted file mode 100644 index 569caf028..000000000 --- a/app/Console/Commands/ClearTasks.php +++ /dev/null @@ -1,80 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Carbon; -use Pterodactyl\Models; -use Illuminate\Console\Command; -use Illuminate\Foundation\Bus\DispatchesJobs; - -class ClearTasks extends Command -{ - use DispatchesJobs; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:tasks:clearlog'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Clears old log entires (> 2 months) from the last log.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $entries = Models\TaskLog::where('run_time', '<=', Carbon::now()->subHours(config('pterodactyl.tasks.clear_log'))->toAtomString())->get(); - - $this->info(sprintf('Preparing to delete %d old task log entries.', count($entries))); - $bar = $this->output->createProgressBar(count($entries)); - - foreach ($entries as &$entry) { - $entry->delete(); - $bar->advance(); - } - - $bar->finish(); - $this->info("\nFinished deleting old logs."); - } -} diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php new file mode 100644 index 000000000..ecf171adc --- /dev/null +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -0,0 +1,194 @@ +. + * + * 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 Pterodactyl\Traits\Commands\EnvironmentWriterTrait; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class AppSettingsCommand extends Command +{ + use EnvironmentWriterTrait; + + const ALLOWED_CACHE_DRIVERS = [ + 'redis' => 'Redis (recommended)', + 'memcached' => 'Memcached', + ]; + + const ALLOWED_SESSION_DRIVERS = [ + 'redis' => 'Redis (recommended)', + 'memcached' => 'Memcached', + 'database' => 'MySQL Database', + 'file' => 'Filesystem', + 'cookie' => 'Cookie', + ]; + + const ALLOWED_QUEUE_DRIVERS = [ + 'redis' => 'Redis (recommended)', + 'database' => 'MySQL Database', + 'sync' => 'Sync', + ]; + + /** + * @var \Illuminate\Contracts\Console\Kernel + */ + protected $command; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var string + */ + protected $description = 'Configure basic environment settings for the Panel.'; + + /** + * @var string + */ + protected $signature = 'p:environment:setup + {--new-salt : Wether or not to generate a new salt for Hashids.} + {--author= : The email that services created on this instance should be linked to.} + {--url= : The URL that this Panel is running on.} + {--timezone= : The timezone to use for Panel times.} + {--cache= : The cache driver backend to use.} + {--session= : The session driver backend to use.} + {--queue= : The queue driver backend to use.} + {--redis-host= : Redis host to use for connections.} + {--redis-pass= : Password used to connect to redis.} + {--redis-port= : Port to connect to redis over.} + {--disable-settings-ui}'; + + /** + * @var array + */ + protected $variables = []; + + /** + * AppSettingsCommand constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Console\Kernel $command + */ + public function __construct(ConfigRepository $config, Kernel $command) + { + parent::__construct(); + + $this->command = $command; + $this->config = $config; + } + + /** + * Handle command execution. + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function handle() + { + if (empty($this->config->get('hashids.salt')) || $this->option('new-salt')) { + $this->variables['HASHIDS_SALT'] = str_random(20); + } + + $this->output->comment(trans('command/messages.environment.app.author_help')); + $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') + ); + + $this->output->comment(trans('command/messages.environment.app.app_url_help')); + $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( + trans('command/messages.environment.app.app_url'), $this->config->get('app.url', 'http://example.org') + ); + + $this->output->comment(trans('command/messages.environment.app.timezone_help')); + $this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate( + trans('command/messages.environment.app.timezone'), + DateTimeZone::listIdentifiers(DateTimeZone::ALL), + $this->config->get('app.timezone') + ); + + $selected = $this->config->get('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 + ); + + $selected = $this->config->get('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 + ); + + $selected = $this->config->get('queue.default', 'redis'); + $this->variables['QUEUE_DRIVER'] = $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 + ); + + if ($this->option('disable-settings-ui')) { + $this->variables['APP_ENVIRONMENT_ONLY'] = 'true'; + } else { + $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(trans('command/messages.environment.app.settings'), true) ? 'false' : 'true'; + } + + $this->checkForRedis(); + $this->writeToEnvironment($this->variables); + + $this->info($this->command->output()); + } + + /** + * Check if redis is selected, if so, request connection details and verify them. + */ + private function checkForRedis() + { + $items = collect($this->variables)->filter(function ($item) { + return $item === 'redis'; + }); + + // Redis was not selected, no need to continue. + if (count($items) === 0) { + return; + } + + $this->output->note(trans('command/messages.environment.app.using_redis')); + $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( + trans('command/messages.environment.app.redis_host'), $this->config->get('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 ($askForRedisPassword) { + $this->output->comment(trans('command/messages.environment.app.redis_pass_help')); + $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( + trans('command/messages.environment.app.redis_password'), function () { + return ''; + } + ); + } + + if (empty($this->variables['REDIS_PASSWORD'])) { + $this->variables['REDIS_PASSWORD'] = 'null'; + } + + $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( + trans('command/messages.environment.app.redis_port'), $this->config->get('database.redis.default.port') + ); + } +} diff --git a/app/Console/Commands/Environment/DatabaseSettingsCommand.php b/app/Console/Commands/Environment/DatabaseSettingsCommand.php new file mode 100644 index 000000000..02396142c --- /dev/null +++ b/app/Console/Commands/Environment/DatabaseSettingsCommand.php @@ -0,0 +1,152 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Environment; + +use PDOException; +use Illuminate\Console\Command; +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 + */ + protected $console; + + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var string + */ + protected $description = 'Configure database settings for the Panel.'; + + /** + * @var string + */ + protected $signature = 'p:environment:database + {--host= : The connection address for the MySQL server.} + {--port= : The connection port for the MySQL server.} + {--database= : The database to use.} + {--username= : Username to use when connecting.} + {--password= : Password to use for this database.}'; + + /** + * @var array + */ + protected $variables = []; + + /** + * DatabaseSettingsCommand constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Database\DatabaseManager $database + * @param \Illuminate\Contracts\Console\Kernel $console + */ + public function __construct(ConfigRepository $config, DatabaseManager $database, Kernel $console) + { + parent::__construct(); + + $this->config = $config; + $this->console = $console; + $this->database = $database; + } + + /** + * Handle command execution. + * + * @return int + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function handle() + { + $this->output->note(trans('command/messages.environment.database.host_warning')); + $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') + ); + + $this->variables['DB_PORT'] = $this->option('port') ?? $this->ask( + trans('command/messages.environment.database.port'), $this->config->get('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') + ); + + $this->output->note(trans('command/messages.environment.database.username_warning')); + $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( + trans('command/messages.environment.database.username'), $this->config->get('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 ($askForMySQLPassword) { + $this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret(trans('command/messages.environment.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')); + + if ($this->confirm(trans('command/messages.environment.database.try_again'))) { + $this->database->disconnect('_pterodactyl_command_test'); + + return $this->handle(); + } + + return 1; + } + + $this->writeToEnvironment($this->variables); + + $this->info($this->console->output()); + + return 0; + } + + /** + * Test that we can connect to the provided MySQL instance and perform a selection. + */ + private function testMySQLConnection() + { + $this->config->set('database.connections._pterodactyl_command_test', [ + 'driver' => 'mysql', + 'host' => $this->variables['DB_HOST'], + 'port' => $this->variables['DB_PORT'], + 'database' => $this->variables['DB_DATABASE'], + 'username' => $this->variables['DB_USERNAME'], + 'password' => $this->variables['DB_PASSWORD'], + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'strict' => true, + ]); + + $this->database->connection('_pterodactyl_command_test')->getPdo(); + } +} diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php new file mode 100644 index 000000000..0403d7f11 --- /dev/null +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -0,0 +1,156 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Environment; + +use Illuminate\Console\Command; +use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class EmailSettingsCommand extends Command +{ + use EnvironmentWriterTrait; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var string + */ + protected $description = 'Set or update the email sending configuration for the Panel.'; + + /** + * @var string + */ + protected $signature = 'p:environment:mail + {--driver= : The mail driver to use.} + {--email= : Email address that messages from the Panel will originate from.} + {--from= : The name emails from the Panel will appear to be from.} + {--encryption=} + {--host=} + {--port=} + {--username=} + {--password=}'; + + /** + * @var array + */ + protected $variables = []; + + /** + * EmailSettingsCommand constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(ConfigRepository $config) + { + parent::__construct(); + + $this->config = $config; + } + + /** + * Handle command execution. + */ + public function handle() + { + $this->variables['MAIL_DRIVER'] = $this->option('driver') ?? $this->choice( + trans('command/messages.environment.mail.ask_driver'), [ + 'smtp' => 'SMTP Server', + 'mail' => 'PHP\'s Internal Mail Function', + 'mailgun' => 'Mailgun Transactional Email', + 'mandrill' => 'Mandrill Transactional Email', + 'postmark' => 'Postmarkapp Transactional Email', + ], $this->config->get('mail.driver', 'smtp') + ); + + $method = 'setup' . studly_case($this->variables['MAIL_DRIVER']) . 'DriverVariables'; + if (method_exists($this, $method)) { + $this->{$method}(); + } + + $this->variables['MAIL_FROM'] = $this->option('email') ?? $this->ask( + trans('command/messages.environment.mail.ask_mail_from'), $this->config->get('mail.from.address') + ); + + $this->variables['MAIL_FROM_NAME'] = $this->option('from') ?? $this->ask( + trans('command/messages.environment.mail.ask_mail_name'), $this->config->get('mail.from.name') + ); + + $this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( + trans('command/messages.environment.mail.ask_encryption'), ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], $this->config->get('mail.encryption', 'tls') + ); + + $this->writeToEnvironment($this->variables); + + $this->line('Updating stored environment configuration file.'); + $this->line(''); + } + + /** + * Handle variables for SMTP driver. + */ + 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->variables['MAIL_PORT'] = $this->option('port') ?? $this->ask( + trans('command/messages.environment.mail.ask_smtp_port'), $this->config->get('mail.port') + ); + + $this->variables['MAIL_USERNAME'] = $this->option('username') ?? $this->ask( + trans('command/messages.environment.mail.ask_smtp_username'), $this->config->get('mail.username') + ); + + $this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret( + trans('command/messages.environment.mail.ask_smtp_password') + ); + } + + /** + * Handle variables for mailgun driver. + */ + private function setupMailgunDriverVariables() + { + $this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask( + trans('command/messages.environment.mail.ask_mailgun_domain'), $this->config->get('services.mailgun.domain') + ); + + $this->variables['MAILGUN_SECRET'] = $this->option('password') ?? $this->ask( + trans('command/messages.environment.mail.ask_mailgun_secret'), $this->config->get('services.mailgun.secret') + ); + } + + /** + * Handle variables for mandrill driver. + */ + private function setupMandrillDriverVariables() + { + $this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask( + trans('command/messages.environment.mail.ask_mandrill_secret'), $this->config->get('services.mandrill.secret') + ); + } + + /** + * Handle variables for postmark driver. + */ + private function setupPostmarkDriverVariables() + { + $this->variables['MAIL_DRIVER'] = 'smtp'; + $this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com'; + $this->variables['MAIL_PORT'] = 587; + $this->variables['MAIL_USERNAME'] = $this->variables['MAIL_PASSWORD'] = $this->option('username') ?? $this->ask( + trans('command/messages.environment.mail.ask_postmark_username'), $this->config->get('mail.username') + ); + } +} diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php new file mode 100644 index 000000000..699adee1f --- /dev/null +++ b/app/Console/Commands/InfoCommand.php @@ -0,0 +1,113 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Helpers\SoftwareVersionService; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class InfoCommand extends Command +{ + /** + * @var string + */ + protected $description = 'Displays the application, database, and email configurations along with the panel version.'; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var string + */ + protected $signature = 'p:info'; + + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + protected $versionService; + + /** + * VersionCommand constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService + */ + public function __construct(ConfigRepository $config, SoftwareVersionService $versionService) + { + parent::__construct(); + + $this->config = $config; + $this->versionService = $versionService; + } + + /** + * Handle execution of command. + */ + 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')], + ['Unique Identifier', $this->config->get('pterodactyl.service.author')], + ], 'compact'); + + $this->output->title('Application Configuration'); + $this->table([], [ + ['Environment', $this->formatText($this->config->get('app.env'), $this->config->get('app.env') === 'production' ?: 'bg=red')], + ['Debug Mode', $this->formatText($this->config->get('app.debug') ? 'Yes' : 'No', ! $this->config->get('app.debug') ?: 'bg=red')], + ['Installation URL', $this->config->get('app.url')], + ['Installation Directory', base_path()], + ['Timezone', $this->config->get('app.timezone')], + ['Cache Driver', $this->config->get('cache.default')], + ['Queue Driver', $this->config->get('queue.default')], + ['Session Driver', $this->config->get('session.driver')], + ['Filesystem Driver', $this->config->get('filesystems.default')], + ['Default Theme', $this->config->get('themes.active')], + ['Proxies', $this->config->get('trustedproxies.proxies')], + ], 'compact'); + + $this->output->title('Database Configuration'); + $driver = $this->config->get('database.default'); + $this->table([], [ + ['Driver', $driver], + ['Host', $this->config->get("database.connections.{$driver}.host")], + ['Port', $this->config->get("database.connections.{$driver}.port")], + ['Database', $this->config->get("database.connections.{$driver}.database")], + ['Usernamne', $this->config->get("database.connections.{$driver}.username")], + ], 'compact'); + + $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')], + ['From Address', $this->config->get('mail.from.address')], + ['From Name', $this->config->get('mail.from.name')], + ['Encryption', $this->config->get('mail.encryption')], + ], 'compact'); + } + + /** + * Format output in a Name: Value manner. + * + * @param string $value + * @param string $opts + * @return string + */ + private function formatText($value, $opts = '') + { + return sprintf('<%s>%s', $opts, $value); + } +} diff --git a/app/Console/Commands/Inspire.php b/app/Console/Commands/Inspire.php deleted file mode 100644 index b579f2c86..000000000 --- a/app/Console/Commands/Inspire.php +++ /dev/null @@ -1,33 +0,0 @@ -comment(PHP_EOL . Inspiring::quote() . PHP_EOL); - } -} diff --git a/app/Console/Commands/Location/DeleteLocationCommand.php b/app/Console/Commands/Location/DeleteLocationCommand.php new file mode 100644 index 000000000..77e5606a3 --- /dev/null +++ b/app/Console/Commands/Location/DeleteLocationCommand.php @@ -0,0 +1,85 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Location; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Locations\LocationDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class DeleteLocationCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Locations\LocationDeletionService + */ + protected $deletionService; + + /** + * @var string + */ + protected $description = 'Deletes a location from the Panel.'; + + /** + * @var \Illuminate\Support\Collection + */ + protected $locations; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:location:delete {--short= : The short code of the location to delete.}'; + + /** + * DeleteLocationCommand constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + */ + public function __construct( + LocationDeletionService $deletionService, + LocationRepositoryInterface $repository + ) { + parent::__construct(); + + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * Respond to the command request. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException + */ + public function handle() + { + $this->locations = $this->locations ?? $this->repository->all(); + $short = $this->option('short') ?? $this->anticipate( + trans('command/messages.location.ask_short'), $this->locations->pluck('short')->toArray() + ); + + $location = $this->locations->where('short', $short)->first(); + if (is_null($location)) { + $this->error(trans('command/messages.location.no_location_found')); + if ($this->input->isInteractive()) { + $this->handle(); + } + + return; + } + + $this->deletionService->handle($location->id); + $this->line(trans('command/messages.location.deleted')); + } +} diff --git a/app/Console/Commands/Location/MakeLocationCommand.php b/app/Console/Commands/Location/MakeLocationCommand.php new file mode 100644 index 000000000..791129727 --- /dev/null +++ b/app/Console/Commands/Location/MakeLocationCommand.php @@ -0,0 +1,62 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Location; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Locations\LocationCreationService; + +class MakeLocationCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Locations\LocationCreationService + */ + protected $creationService; + + /** + * @var string + */ + protected $signature = 'p:location:make + {--short= : The shortcode name of this location (ex. us1).} + {--long= : A longer description of this location.}'; + + /** + * @var string + */ + protected $description = 'Creates a new location on the system via the CLI.'; + + /** + * Create a new command instance. + * + * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService + */ + public function __construct(LocationCreationService $creationService) + { + parent::__construct(); + + $this->creationService = $creationService; + } + + /** + * Handle the command execution process. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle() + { + $short = $this->option('short') ?? $this->ask(trans('command/messages.location.ask_short')); + $long = $this->option('long') ?? $this->ask(trans('command/messages.location.ask_long')); + + $location = $this->creationService->handle(compact('short', 'long')); + $this->line(trans('command/messages.location.created', [ + 'name' => $location->short, + 'id' => $location->id, + ])); + } +} diff --git a/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php new file mode 100644 index 000000000..1b5ded4d6 --- /dev/null +++ b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php @@ -0,0 +1,56 @@ +disk = $filesystem->disk(); + } + + /** + * Handle command execution. + */ + public function handle() + { + $files = $this->disk->files('services/.bak'); + + collect($files)->each(function (SplFileInfo $file) { + $lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath())); + if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) { + $this->disk->delete($file->getPath()); + $this->info(trans('command/messages.maintenance.deleting_service_backup', ['file' => $file->getFilename()])); + } + }); + } +} diff --git a/app/Console/Commands/MakeUser.php b/app/Console/Commands/MakeUser.php deleted file mode 100644 index 05803dd3b..000000000 --- a/app/Console/Commands/MakeUser.php +++ /dev/null @@ -1,91 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Illuminate\Console\Command; -use Pterodactyl\Repositories\UserRepository; - -class MakeUser extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:user - {--firstname= : First name to use for this account.} - {--lastname= : Last name to use for this account.} - {--username= : Username to use for this account.} - {--email= : Email address to use for this account.} - {--password= : Password to assign to the user.} - {--admin= : Boolean flag for if user should be an admin.}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Create a user within the panel.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $data['name_first'] = is_null($this->option('firstname')) ? $this->ask('First Name') : $this->option('firstname'); - $data['name_last'] = is_null($this->option('lastname')) ? $this->ask('Last Name') : $this->option('lastname'); - $data['username'] = is_null($this->option('username')) ? $this->ask('Username') : $this->option('username'); - $data['email'] = is_null($this->option('email')) ? $this->ask('Email') : $this->option('email'); - $data['password'] = is_null($this->option('password')) ? $this->secret('Password') : $this->option('password'); - $password_confirmation = is_null($this->option('password')) ? $this->secret('Confirm Password') : $this->option('password'); - - if ($data['password'] !== $password_confirmation) { - return $this->error('The passwords provided did not match!'); - } - - $data['root_admin'] = is_null($this->option('admin')) ? $this->confirm('Is this user a root administrator?') : $this->option('admin'); - - try { - $user = new UserRepository; - $user->create($data); - - return $this->info('User successfully created.'); - } catch (\Exception $ex) { - return $this->error($ex->getMessage()); - } - } -} diff --git a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php new file mode 100644 index 000000000..b9e007ee7 --- /dev/null +++ b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php @@ -0,0 +1,58 @@ +repository = $repository; + } + + /** + * Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7. + * + * @return null|void + */ + public function handle() + { + $count = $this->repository->findCountWhere([['key_type', '=', ApiKey::TYPE_NONE]]); + $continue = $this->confirm( + 'This action will remove ' . $count . ' keys from the database. Are you sure you wish to continue?', false + ); + + if (! $continue) { + return null; + } + + $this->info('Deleting keys...'); + $this->repository->deleteWhere([['key_type', '=', ApiKey::TYPE_NONE]]); + $this->info('Keys were successfully deleted.'); + } +} diff --git a/app/Console/Commands/RebuildServer.php b/app/Console/Commands/RebuildServer.php deleted file mode 100644 index 2177b112a..000000000 --- a/app/Console/Commands/RebuildServer.php +++ /dev/null @@ -1,146 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; -use Illuminate\Console\Command; - -class RebuildServer extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:rebuild - {--all} - {--node= : Id of node to rebuild all servers on.} - {--server= : UUID of server to rebuild.}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Rebuild docker containers for a server or multiple servers.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - if ($this->option('all')) { - $servers = Server::all(); - } elseif ($this->option('node')) { - $servers = Server::where('node_id', $this->option('node'))->get(); - } elseif ($this->option('server')) { - $servers = Server::where('id', $this->option('server'))->get(); - } else { - $this->error('You must pass a flag to determine which server(s) to rebuild.'); - - return; - } - - $servers->load('node', 'service', 'option.variables', 'pack'); - - $this->line('Beginning processing, do not exit this script.'); - $bar = $this->output->createProgressBar(count($servers)); - $results = collect([]); - foreach ($servers as $server) { - try { - $environment = $server->option->variables->map(function ($item, $key) use ($server) { - $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - - return [ - 'variable' => $item->env_variable, - 'value' => (! is_null($display)) ? $display : $item->default_value, - ]; - }); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => [ - 'image' => $server->image, - 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup]), - ], - 'service' => [ - 'type' => $server->service->folder, - 'option' => $server->option->tag, - 'pack' => ! is_null($server->pack) ? $server->pack->uuid : null, - ], - ], - ]); - - $results = $results->merge([ - $server->uuid => [ - 'status' => 'info', - 'messages' => [ - '[✓] Processed rebuild request for ' . $server->uuid, - ], - ], - ]); - } catch (\Exception $ex) { - $results = $results->merge([ - $server->uuid => [ - 'status' => 'error', - 'messages' => [ - '[✗] Failed to process rebuild request for ' . $server->uuid, - $ex->getMessage(), - ], - ], - ]); - } - - $bar->advance(); - } - - $bar->finish(); - $console = $this; - - $this->line("\n"); - $results->each(function ($item, $key) use ($console) { - foreach ($item['messages'] as $line) { - $console->{$item['status']}($line); - } - }); - $this->line("\nCompleted rebuild command processing."); - } -} diff --git a/app/Console/Commands/RunTasks.php b/app/Console/Commands/RunTasks.php deleted file mode 100644 index 6c3ccc6c9..000000000 --- a/app/Console/Commands/RunTasks.php +++ /dev/null @@ -1,81 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Carbon; -use Pterodactyl\Models\Task; -use Illuminate\Console\Command; -use Pterodactyl\Jobs\SendScheduledTask; -use Illuminate\Foundation\Bus\DispatchesJobs; - -class RunTasks extends Command -{ - use DispatchesJobs; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:tasks'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Find and run scheduled tasks.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $tasks = Task::where('queued', false)->where('active', true)->where('next_run', '<=', Carbon::now()->toAtomString())->get(); - - $this->info(sprintf('Preparing to queue %d tasks.', count($tasks))); - $bar = $this->output->createProgressBar(count($tasks)); - - foreach ($tasks as &$task) { - $bar->advance(); - $this->dispatch((new SendScheduledTask($task))->onQueue(config('pterodactyl.queues.low'))); - } - - $bar->finish(); - $this->info("\nFinished queuing tasks for running."); - } -} diff --git a/app/Console/Commands/Schedule/ProcessRunnableCommand.php b/app/Console/Commands/Schedule/ProcessRunnableCommand.php new file mode 100644 index 000000000..88646c030 --- /dev/null +++ b/app/Console/Commands/Schedule/ProcessRunnableCommand.php @@ -0,0 +1,94 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Schedule; + +use Carbon\Carbon; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; +use Pterodactyl\Services\Schedules\ProcessScheduleService; +use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; + +class ProcessRunnableCommand extends Command +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var string + */ + protected $description = 'Process schedules in the database and determine which are ready to run.'; + + /** + * @var \Pterodactyl\Services\Schedules\ProcessScheduleService + */ + protected $processScheduleService; + + /** + * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:schedule:process'; + + /** + * ProcessRunnableCommand constructor. + * + * @param \Carbon\Carbon $carbon + * @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService + * @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository + */ + public function __construct( + Carbon $carbon, + ProcessScheduleService $processScheduleService, + ScheduleRepositoryInterface $repository + ) { + parent::__construct(); + + $this->carbon = $carbon; + $this->processScheduleService = $processScheduleService; + $this->repository = $repository; + } + + /** + * Handle command execution. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle() + { + $schedules = $this->repository->getSchedulesToProcess($this->carbon->now()->toAtomString()); + + $bar = $this->output->createProgressBar(count($schedules)); + $schedules->each(function ($schedule) use ($bar) { + if ($schedule->tasks instanceof Collection && count($schedule->tasks) > 0) { + $this->processScheduleService->handle($schedule); + + if ($this->input->isInteractive()) { + $bar->clear(); + $this->line(trans('command/messages.schedule.output_line', [ + 'schedule' => $schedule->name, + 'hash' => $schedule->hashid, + ])); + } + } + + $bar->advance(); + $bar->display(); + }); + + $this->line(''); + } +} diff --git a/app/Console/Commands/Server/RebuildServerCommand.php b/app/Console/Commands/Server/RebuildServerCommand.php new file mode 100644 index 000000000..ac239b1ee --- /dev/null +++ b/app/Console/Commands/Server/RebuildServerCommand.php @@ -0,0 +1,109 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\Server; + +use Webmozart\Assert\Assert; +use Illuminate\Console\Command; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class RebuildServerCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + protected $configurationStructureService; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var string + */ + protected $description = 'Rebuild a single server, all servers on a node, or all servers on the panel.'; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:server:rebuild + {server? : The ID of the server to rebuild.} + {--node= : ID of the node to rebuild all servers on. Ignored if server is passed.}'; + + /** + * RebuildServerCommand constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct( + DaemonServerRepositoryInterface $daemonRepository, + ServerConfigurationStructureService $configurationStructureService, + ServerRepositoryInterface $repository + ) { + parent::__construct(); + + $this->configurationStructureService = $configurationStructureService; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + } + + /** + * Handle command execution. + */ + public function handle() + { + $servers = $this->getServersToProcess(); + $bar = $this->output->createProgressBar(count($servers)); + + $servers->each(function ($server) use ($bar) { + $bar->clear(); + $json = array_merge($this->configurationStructureService->handle($server), ['rebuild' => true]); + + try { + $this->daemonRepository->setServer($server)->update($json); + } catch (RequestException $exception) { + $this->output->error(trans('command/messages.server.rebuild_failed', [ + 'name' => $server->name, + 'id' => $server->id, + 'node' => $server->node->name, + 'message' => $exception->getMessage(), + ])); + } + + $bar->advance(); + $bar->display(); + }); + + $this->line(''); + } + + /** + * Return the servers to be rebuilt. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + private function getServersToProcess() + { + Assert::nullOrIntegerish($this->argument('server'), 'Value passed in server argument must be null or an integer, received %s.'); + Assert::nullOrIntegerish($this->option('node'), 'Value passed in node option must be null or integer, received %s.'); + + return $this->repository->getDataForRebuild($this->argument('server'), $this->option('node')); + } +} diff --git a/app/Console/Commands/ShowVersion.php b/app/Console/Commands/ShowVersion.php deleted file mode 100644 index 199a905b1..000000000 --- a/app/Console/Commands/ShowVersion.php +++ /dev/null @@ -1,65 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Version; -use Illuminate\Console\Command; - -class ShowVersion extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:version'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Display current panel version.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $this->info('You are running Pterodactyl Panel v' . Version::getCurrentPanel() . ' (' . ((Version::isLatestPanel()) ? 'Up to Date' : 'Latest: ' . Version::getDaemon()) . ')'); - } -} diff --git a/app/Console/Commands/UpdateEmailSettings.php b/app/Console/Commands/UpdateEmailSettings.php deleted file mode 100644 index 1e316d6cb..000000000 --- a/app/Console/Commands/UpdateEmailSettings.php +++ /dev/null @@ -1,170 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Illuminate\Console\Command; - -class UpdateEmailSettings extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:mail - {--driver=} - {--email=} - {--from-name=} - {--host=} - {--port=} - {--username=} - {--password=}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Sets or updates email settings for the .env file.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $variables = []; - $file = base_path() . '/.env'; - if (! file_exists($file)) { - $this->error('Missing environment file! It appears that you have not installed this panel correctly.'); - exit(); - } - - $envContents = file_get_contents($file); - - $this->table([ - 'Option', - 'Description', - ], [ - [ - 'smtp', - 'SMTP Server Email', - ], - [ - 'mail', - 'PHP\'s Internal Mail Server', - ], - [ - 'mailgun', - 'Mailgun Email Service', - ], - [ - 'mandrill', - 'Mandrill Transactional Email Service', - ], - [ - 'postmark', - 'Postmark Transactional Email Service', - ], - ]); - - $variables['MAIL_DRIVER'] = is_null($this->option('driver')) ? $this->choice('Which email driver would you like to use?', [ - 'smtp', - 'mail', - 'mailgun', - 'mandrill', - 'postmark', - ]) : $this->option('driver'); - - switch ($variables['MAIL_DRIVER']) { - case 'smtp': - $variables['MAIL_HOST'] = is_null($this->option('host')) ? $this->ask('SMTP Host (e.g smtp.google.com)', config('mail.host')) : $this->option('host'); - $variables['MAIL_PORT'] = is_null($this->option('port')) ? $this->anticipate('SMTP Host Port (e.g 587)', ['587', config('mail.port')], config('mail.port')) : $this->option('port'); - $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('SMTP Username', config('mail.username')) : $this->option('password'); - $variables['MAIL_PASSWORD'] = is_null($this->option('password')) ? $this->secret('SMTP Password') : $this->option('password'); - break; - case 'mail': - break; - case 'mailgun': - $variables['MAILGUN_DOMAIN'] = is_null($this->option('host')) ? $this->ask('Mailgun Domain') : $this->option('host'); - $variables['MAILGUN_KEY'] = is_null($this->option('username')) ? $this->ask('Mailgun Key') : $this->option('username'); - break; - case 'mandrill': - $variables['MANDRILL_SECRET'] = is_null($this->option('username')) ? $this->ask('Mandrill Secret') : $this->option('username'); - break; - case 'postmark': - $variables['MAIL_DRIVER'] = 'smtp'; - $variables['MAIL_HOST'] = 'smtp.postmarkapp.com'; - $variables['MAIL_PORT'] = 587; - $variables['MAIL_USERNAME'] = is_null($this->option('username')) ? $this->ask('Postmark API Token', config('mail.username')) : $this->option('username'); - $variables['MAIL_PASSWORD'] = $variables['MAIL_USERNAME']; - break; - default: - $this->error('No email service was defined!'); - exit(); - break; - } - - $variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from', config('mail.from.address')) : $this->option('email'); - $variables['MAIL_FROM_NAME'] = is_null($this->option('from-name')) ? $this->ask('Name emails should appear to be from', config('mail.from.name')) : $this->option('from-name'); - $variables['MAIL_FROM_NAME'] = '"' . $variables['MAIL_FROM_NAME'] . '"'; - $variables['MAIL_ENCRYPTION'] = 'tls'; - - $bar = $this->output->createProgressBar(count($variables)); - - $this->line('Writing new email environment configuration to file.'); - foreach ($variables as $key => $value) { - if (str_contains($value, ' ') && ! str_contains($value, '"')) { - $value = '"' . $value . '"'; - } - $newValue = $key . '=' . $value . ' # DO NOT EDIT! set using pterodactyl:mail'; - - if (preg_match_all('/^' . $key . '=(.*)$/m', $envContents) < 1) { - $envContents = $envContents . "\n" . $newValue; - } else { - $envContents = preg_replace('/^' . $key . '=(.*)$/m', $newValue, $envContents); - } - $bar->advance(); - } - - file_put_contents($file, $envContents); - $bar->finish(); - - $this->line('Updating evironment configuration cache file.'); - $this->call('config:cache'); - echo "\n"; - } -} diff --git a/app/Console/Commands/UpdateEnvironment.php b/app/Console/Commands/UpdateEnvironment.php deleted file mode 100644 index 6252a2824..000000000 --- a/app/Console/Commands/UpdateEnvironment.php +++ /dev/null @@ -1,208 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Console\Commands; - -use Uuid; -use Illuminate\Console\Command; - -class UpdateEnvironment extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:env - {--dbhost=} - {--dbport=} - {--dbname=} - {--dbuser=} - {--dbpass=} - {--url=} - {--driver=} - {--session-driver=} - {--queue-driver=} - {--timezone=}'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Update environment settings automatically.'; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $variables = []; - $file = base_path() . '/.env'; - if (! file_exists($file)) { - $this->error('Missing environment file! It appears that you have not installed this panel correctly.'); - exit(); - } - - $envContents = file_get_contents($file); - - $this->info('Simply leave blank and press enter to fields that you do not wish to update.'); - if (is_null(config('pterodactyl.service.author', null))) { - $this->info('No service author set, setting one now.'); - $variables['SERVICE_AUTHOR'] = (string) Uuid::generate(4); - } - - if (isset($variables['APP_THEME'])) { - if ($variables['APP_THEME'] === 'default') { - $variables['APP_THEME'] = 'pterodactyl'; - } - } - - if (is_null($this->option('dbhost'))) { - $variables['DB_HOST'] = $this->anticipate('Database Host', ['localhost', '127.0.0.1', config('database.connections.mysql.host')], config('database.connections.mysql.host')); - } else { - $variables['DB_HOST'] = $this->option('dbhost'); - } - - if (is_null($this->option('dbport'))) { - $variables['DB_PORT'] = $this->anticipate('Database Port', [3306, config('database.connections.mysql.port')], config('database.connections.mysql.port')); - } else { - $variables['DB_PORT'] = $this->option('dbport'); - } - - if (is_null($this->option('dbname'))) { - $variables['DB_DATABASE'] = $this->anticipate('Database Name', ['pterodactyl', 'homestead', config('database.connections.mysql.database')], config('database.connections.mysql.database')); - } else { - $variables['DB_DATABASE'] = $this->option('dbname'); - } - - if (is_null($this->option('dbuser'))) { - $variables['DB_USERNAME'] = $this->anticipate('Database Username', [config('database.connections.mysql.username')], config('database.connections.mysql.username')); - } else { - $variables['DB_USERNAME'] = $this->option('dbuser'); - } - - if (is_null($this->option('dbpass'))) { - $this->line('The Database Password field is required; you cannot hit enter and use a default value.'); - $variables['DB_PASSWORD'] = $this->secret('Database User Password'); - } else { - $variables['DB_PASSWORD'] = $this->option('dbpass'); - } - - if (is_null($this->option('url'))) { - $variables['APP_URL'] = $this->ask('Panel URL (include http(s)://)', config('app.url')); - } else { - $variables['APP_URL'] = $this->option('url'); - } - - if (is_null($this->option('timezone'))) { - $this->line('The timezone should match one of the supported timezones according to http://php.net/manual/en/timezones.php'); - $variables['APP_TIMEZONE'] = $this->anticipate('Panel Timezone', \DateTimeZone::listIdentifiers(\DateTimeZone::ALL), config('app.timezone')); - } else { - $variables['APP_TIMEZONE'] = $this->option('timezone'); - } - - if (is_null($this->option('driver'))) { - $options = [ - 'memcached' => 'Memcache', - 'redis' => 'Redis (recommended)', - 'apc' => 'APC', - 'array' => 'PHP Array', - ]; - $default = (in_array(config('cache.default', 'memcached'), $options)) ? config('cache.default', 'memcached') : 'memcached'; - - $this->line('If you chose redis as your cache driver backend, you *must* have a redis server configured already.'); - $variables['CACHE_DRIVER'] = $this->choice('Which cache driver backend would you like to use?', $options, $default); - } else { - $variables['CACHE_DRIVER'] = $this->option('driver'); - } - - if (is_null($this->option('session-driver'))) { - $options = [ - 'database' => 'MySQL (recommended)', - 'redis' => 'Redis', - 'file' => 'File', - 'cookie' => 'Cookie', - 'apc' => 'APC', - 'array' => 'PHP Array', - ]; - $default = (in_array(config('session.driver', 'database'), $options)) ? config('cache.default', 'database') : 'database'; - - $this->line('If you chose redis as your cache driver backend, you *must* have a redis server configured already.'); - $variables['SESSION_DRIVER'] = $this->choice('Which session driver backend would you like to use?', $options, $default); - } else { - $variables['SESSION_DRIVER'] = $this->option('session-driver'); - } - - if (is_null($this->option('queue-driver'))) { - $options = [ - 'database' => 'Database (recommended)', - 'redis' => 'Redis', - 'sqs' => 'Amazon SQS', - 'sync' => 'Sync', - 'null' => 'None', - ]; - $default = (in_array(config('queue.driver', 'database'), $options)) ? config('queue.driver', 'database') : 'database'; - - $this->line('If you chose redis as your queue driver backend, you *must* have a redis server configured already.'); - $variables['QUEUE_DRIVER'] = $this->choice('Which queue driver backend would you like to use?', $options, $default); - } else { - $variables['QUEUE_DRIVER'] = $this->option('queue-driver'); - } - - $bar = $this->output->createProgressBar(count($variables)); - - foreach ($variables as $key => $value) { - if (str_contains($value, ' ') && ! str_contains($value, '"')) { - $value = '"' . $value . '"'; - } - $newValue = $key . '=' . $value . ' # DO NOT EDIT! set using pterodactyl:env'; - - if (preg_match_all('/^' . $key . '=(.*)$/m', $envContents) < 1) { - $envContents = $envContents . "\n" . $newValue; - } else { - $envContents = preg_replace('/^' . $key . '=(.*)$/m', $newValue, $envContents); - } - $bar->advance(); - } - - file_put_contents($file, $envContents); - $bar->finish(); - - $this->call('config:cache'); - $this->line("\n"); - } -} diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php new file mode 100644 index 000000000..c9a69bee8 --- /dev/null +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -0,0 +1,99 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\User; + +use Webmozart\Assert\Assert; +use Illuminate\Console\Command; +use Pterodactyl\Services\Users\UserDeletionService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class DeleteUserCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Users\UserDeletionService + */ + protected $deletionService; + + /** + * @var string + */ + protected $description = 'Deletes a user from the Panel if no servers are attached to their account.'; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:user:delete {--user=}'; + + /** + * DeleteUserCommand constructor. + * + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + UserDeletionService $deletionService, + UserRepositoryInterface $repository + ) { + parent::__construct(); + + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * @return bool + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle() + { + $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); + Assert::notEmpty($search, 'Search term must be a non-null value, received %s.'); + + $results = $this->repository->setSearchTerm($search)->all(); + if (count($results) < 1) { + $this->error(trans('command/messages.user.no_users_found')); + if ($this->input->isInteractive()) { + return $this->handle(); + } + + return false; + } + + if ($this->input->isInteractive()) { + $tableValues = []; + foreach ($results as $user) { + $tableValues[] = [$user->id, $user->email, $user->name]; + } + + $this->table(['User ID', 'Email', 'Name'], $tableValues); + if (! $deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) { + return $this->handle(); + } + } else { + if (count($results) > 1) { + $this->error(trans('command/messages.user.multiple_found')); + + return false; + } + + $deleteUser = $results->first(); + } + + if ($this->confirm(trans('command/messages.user.confirm_delete')) || ! $this->input->isInteractive()) { + $this->deletionService->handle($deleteUser); + $this->info(trans('command/messages.user.deleted')); + } + } +} diff --git a/app/Console/Commands/User/DisableTwoFactorCommand.php b/app/Console/Commands/User/DisableTwoFactorCommand.php new file mode 100644 index 000000000..f2cab910d --- /dev/null +++ b/app/Console/Commands/User/DisableTwoFactorCommand.php @@ -0,0 +1,65 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\User; + +use Illuminate\Console\Command; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class DisableTwoFactorCommand extends Command +{ + /** + * @var string + */ + protected $description = 'Disable two-factor authentication for a specific user in the Panel.'; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:user:disable2fa {--email= : The email of the user to disable 2-Factor for.}'; + + /** + * DisableTwoFactorCommand constructor. + * + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct(UserRepositoryInterface $repository) + { + parent::__construct(); + + $this->repository = $repository; + } + + /** + * Handle command execution process. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle() + { + if ($this->input->isInteractive()) { + $this->output->warning(trans('command/messages.user.2fa_help_text')); + } + + $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); + $user = $this->repository->setColumns(['id', 'email'])->findFirstWhere([['email', '=', $email]]); + + $this->repository->withoutFreshModel()->update($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ]); + $this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email])); + } +} diff --git a/app/Console/Commands/User/MakeUserCommand.php b/app/Console/Commands/User/MakeUserCommand.php new file mode 100644 index 000000000..35060b53f --- /dev/null +++ b/app/Console/Commands/User/MakeUserCommand.php @@ -0,0 +1,73 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Console\Commands\User; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Users\UserCreationService; + +class MakeUserCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $creationService; + + /** + * @var string + */ + protected $description = 'Creates a user on the system via the CLI.'; + + /** + * @var string + */ + protected $signature = 'p:user:make {--email=} {--username=} {--name-first=} {--name-last=} {--password=} {--admin=} {--no-password}'; + + /** + * MakeUserCommand constructor. + * + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + */ + public function __construct(UserCreationService $creationService) + { + parent::__construct(); + + $this->creationService = $creationService; + } + + /** + * Handle command request to create a new user. + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + 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')); + $this->line(trans('command/messages.user.ask_password_tip')); + $password = $this->secret(trans('command/messages.user.ask_password')); + } + + $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', '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/Console/Kernel.php b/app/Console/Kernel.php index bba5bb66e..c87cd5394 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -8,35 +8,21 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { /** - * The Artisan commands provided by your application. - * - * @var array + * Register the commands for the application. */ - protected $commands = [ - \Pterodactyl\Console\Commands\Inspire::class, - \Pterodactyl\Console\Commands\MakeUser::class, - \Pterodactyl\Console\Commands\ShowVersion::class, - \Pterodactyl\Console\Commands\UpdateEnvironment::class, - \Pterodactyl\Console\Commands\RunTasks::class, - \Pterodactyl\Console\Commands\ClearTasks::class, - \Pterodactyl\Console\Commands\ClearServices::class, - \Pterodactyl\Console\Commands\UpdateEmailSettings::class, - \Pterodactyl\Console\Commands\CleanServiceBackup::class, - \Pterodactyl\Console\Commands\AddNode::class, - \Pterodactyl\Console\Commands\AddLocation::class, - \Pterodactyl\Console\Commands\RebuildServer::class, - ]; + protected function commands() + { + $this->load(__DIR__ . '/Commands'); + } /** * Define the application's command schedule. * - * @param \Illuminate\Console\Scheduling\Schedule $schedule - * @return void + * @param \Illuminate\Console\Scheduling\Schedule $schedule */ protected function schedule(Schedule $schedule) { - $schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping(); - $schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15); - $schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13); + $schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping(); + $schedule->command('p:maintenance:clean-service-backups')->daily(); } } diff --git a/app/Contracts/Criteria/CriteriaInterface.php b/app/Contracts/Criteria/CriteriaInterface.php new file mode 100644 index 000000000..628aee94e --- /dev/null +++ b/app/Contracts/Criteria/CriteriaInterface.php @@ -0,0 +1,24 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Criteria; + +use Pterodactyl\Repositories\Repository; + +interface CriteriaInterface +{ + /** + * Apply selected criteria to a repository call. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param \Pterodactyl\Repositories\Repository $repository + * @return mixed + */ + public function apply($model, Repository $repository); +} diff --git a/app/Contracts/Extensions/HashidsInterface.php b/app/Contracts/Extensions/HashidsInterface.php new file mode 100644 index 000000000..39fa7d624 --- /dev/null +++ b/app/Contracts/Extensions/HashidsInterface.php @@ -0,0 +1,26 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Extensions; + +use Hashids\HashidsInterface as VendorHashidsInterface; + +interface HashidsInterface extends VendorHashidsInterface +{ + /** + * Decode an encoded hashid and return the first result. + * + * @param string $encoded + * @param null $default + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function decodeFirst($encoded, $default = null); +} diff --git a/app/Contracts/Repository/AllocationRepositoryInterface.php b/app/Contracts/Repository/AllocationRepositoryInterface.php new file mode 100644 index 000000000..1331e906f --- /dev/null +++ b/app/Contracts/Repository/AllocationRepositoryInterface.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ApiPermissionRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/Attributes/SearchableInterface.php b/app/Contracts/Repository/Attributes/SearchableInterface.php new file mode 100644 index 000000000..f1ab6e804 --- /dev/null +++ b/app/Contracts/Repository/Attributes/SearchableInterface.php @@ -0,0 +1,38 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Models\Database; +use Illuminate\Support\Collection; + +interface DatabaseRepositoryInterface extends RepositoryInterface +{ + const DEFAULT_CONNECTION_NAME = 'dynamic'; + + /** + * Set the connection name to execute statements against. + * + * @param string $connection + * @return $this + */ + public function setConnection(string $connection); + + /** + * Return the connection to execute statements aganist. + * + * @return string + */ + public function getConnection(): string; + + /** + * Return all of the databases belonging to a server. + * + * @param int $server + * @return \Illuminate\Support\Collection + */ + public function getDatabasesForServer(int $server): Collection; + + /** + * Create a new database if it does not already exist on the host with + * the provided details. + * + * @param array $data + * @return \Pterodactyl\Models\Database + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException + */ + public function createIfNotExists(array $data): Database; + + /** + * Create a new database on a given connection. + * + * @param string $database + * @return bool + */ + public function createDatabase(string $database): bool; + + /** + * Create a new database user on a given connection. + * + * @param string $username + * @param string $remote + * @param string $password + * @return bool + */ + public function createUser(string $username, string $remote, string $password): bool; + + /** + * Give a specific user access to a given database. + * + * @param string $database + * @param string $username + * @param string $remote + * @return bool + */ + public function assignUserToDatabase(string $database, string $username, string $remote): bool; + + /** + * Flush the privileges for a given connection. + * + * @return bool + */ + public function flush(): bool; + + /** + * Drop a given database on a specific connection. + * + * @param string $database + * @return bool + */ + public function dropDatabase(string $database): bool; + + /** + * Drop a given user on a specific connection. + * + * @param string $username + * @param string $remote + * @return mixed + */ + public function dropUser(string $username, string $remote): bool; +} diff --git a/app/Contracts/Repository/EggRepositoryInterface.php b/app/Contracts/Repository/EggRepositoryInterface.php new file mode 100644 index 000000000..364fd0c6d --- /dev/null +++ b/app/Contracts/Repository/EggRepositoryInterface.php @@ -0,0 +1,61 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Models\Egg; +use Illuminate\Database\Eloquent\Collection; + +interface EggRepositoryInterface extends RepositoryInterface +{ + /** + * Return an egg with the variables relation attached. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithVariables(int $id): Egg; + + /** + * Return all eggs and their relations to be used in the daemon API. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAllWithCopyAttributes(): Collection; + + /** + * Return an egg with the scriptFrom and configFrom relations loaded onto the model. + * + * @param int|string $value + * @param string $column + * @return \Pterodactyl\Models\Egg + */ + public function getWithCopyAttributes($value, string $column = 'id'): Egg; + + /** + * Return all of the data needed to export a service. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithExportAttributes(int $id): Egg; + + /** + * Confirm a copy script belongs to the same nest as the item trying to use it. + * + * @param int $copyFromId + * @param int $service + * @return bool + */ + public function isCopiableScript(int $copyFromId, int $service): bool; +} diff --git a/app/Contracts/Repository/EggVariableRepositoryInterface.php b/app/Contracts/Repository/EggVariableRepositoryInterface.php new file mode 100644 index 000000000..77b46f96d --- /dev/null +++ b/app/Contracts/Repository/EggVariableRepositoryInterface.php @@ -0,0 +1,24 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Illuminate\Support\Collection; + +interface EggVariableRepositoryInterface extends RepositoryInterface +{ + /** + * Return editable variables for a given egg. Editable variables must be set to + * user viewable in order to be picked up by this function. + * + * @param int $egg + * @return \Illuminate\Support\Collection + */ + public function getEditableVariables(int $egg): Collection; +} diff --git a/app/Contracts/Repository/LocationRepositoryInterface.php b/app/Contracts/Repository/LocationRepositoryInterface.php new file mode 100644 index 000000000..e91da6c43 --- /dev/null +++ b/app/Contracts/Repository/LocationRepositoryInterface.php @@ -0,0 +1,44 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Models\Nest; + +interface NestRepositoryInterface extends RepositoryInterface +{ + /** + * Return a nest or all nests with their associated eggs, variables, and packs. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggs(int $id = null); + + /** + * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * + * @param int|null $id + * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithCounts(int $id = null); + + /** + * Return a nest along with its associated eggs and the servers relation on those eggs. + * + * @param int $id + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggServers(int $id): Nest; +} diff --git a/app/Contracts/Repository/NodeRepositoryInterface.php b/app/Contracts/Repository/NodeRepositoryInterface.php new file mode 100644 index 000000000..0ebcbe3a0 --- /dev/null +++ b/app/Contracts/Repository/NodeRepositoryInterface.php @@ -0,0 +1,77 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +interface PermissionRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php new file mode 100644 index 000000000..4a098c34f --- /dev/null +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -0,0 +1,203 @@ +. -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * Pterodactyl - Panel + * Copyright (c) 2015 - 2017 Dane Everitt . + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ namespace Pterodactyl\Events\Auth; @@ -47,9 +32,8 @@ class FailedCaptcha /** * Create a new event instance. * - * @param string $ip - * @param string $domain - * @return void + * @param string $ip + * @param string $domain */ public function __construct($ip, $domain) { diff --git a/app/Events/Auth/FailedPasswordReset.php b/app/Events/Auth/FailedPasswordReset.php index ec2de3b47..913bd0a6f 100644 --- a/app/Events/Auth/FailedPasswordReset.php +++ b/app/Events/Auth/FailedPasswordReset.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Auth; @@ -47,9 +32,8 @@ class FailedPasswordReset /** * Create a new event instance. * - * @param string $ip - * @param string $email - * @return void + * @param string $ip + * @param string $email */ public function __construct($ip, $email) { diff --git a/app/Events/Event.php b/app/Events/Event.php index af6056df3..2db145df6 100644 --- a/app/Events/Event.php +++ b/app/Events/Event.php @@ -4,5 +4,4 @@ namespace Pterodactyl\Events; abstract class Event { - // } diff --git a/app/Events/Server/Created.php b/app/Events/Server/Created.php index 2591cd5af..8ca547004 100644 --- a/app/Events/Server/Created.php +++ b/app/Events/Server/Created.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Creating.php b/app/Events/Server/Creating.php index 46e4898c1..318156b6d 100644 --- a/app/Events/Server/Creating.php +++ b/app/Events/Server/Creating.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Deleted.php b/app/Events/Server/Deleted.php index 6f8709b85..258ac75b1 100644 --- a/app/Events/Server/Deleted.php +++ b/app/Events/Server/Deleted.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Deleting.php b/app/Events/Server/Deleting.php index 3152ed96e..49219afc9 100644 --- a/app/Events/Server/Deleting.php +++ b/app/Events/Server/Deleting.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Saved.php b/app/Events/Server/Saved.php index d19659045..4b8ae2165 100644 --- a/app/Events/Server/Saved.php +++ b/app/Events/Server/Saved.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Saved /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Saving.php b/app/Events/Server/Saving.php index 1ae31da32..9bb8153fe 100644 --- a/app/Events/Server/Saving.php +++ b/app/Events/Server/Saving.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Saving /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Updated.php b/app/Events/Server/Updated.php index 1284ea504..7157d4b34 100644 --- a/app/Events/Server/Updated.php +++ b/app/Events/Server/Updated.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Updated /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Updating.php b/app/Events/Server/Updating.php index b2fc255c8..c91c8a122 100644 --- a/app/Events/Server/Updating.php +++ b/app/Events/Server/Updating.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Server; @@ -41,8 +26,7 @@ class Updating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Subuser/Created.php b/app/Events/Subuser/Created.php index 9a2d28536..63941b2af 100644 --- a/app/Events/Subuser/Created.php +++ b/app/Events/Subuser/Created.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Subuser; @@ -41,8 +26,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Creating.php b/app/Events/Subuser/Creating.php index 5083c497c..5d6f6d800 100644 --- a/app/Events/Subuser/Creating.php +++ b/app/Events/Subuser/Creating.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Subuser; @@ -41,8 +26,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Deleted.php b/app/Events/Subuser/Deleted.php index 1e419ab81..ee53388b7 100644 --- a/app/Events/Subuser/Deleted.php +++ b/app/Events/Subuser/Deleted.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Subuser; @@ -41,8 +26,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Deleting.php b/app/Events/Subuser/Deleting.php index a06ebe2dc..6d25a3ebb 100644 --- a/app/Events/Subuser/Deleting.php +++ b/app/Events/Subuser/Deleting.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\Subuser; @@ -41,8 +26,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/User/Created.php b/app/Events/User/Created.php index 4e1fd9e75..b66d531d4 100644 --- a/app/Events/User/Created.php +++ b/app/Events/User/Created.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\User; @@ -41,8 +26,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Creating.php b/app/Events/User/Creating.php index cc544af9e..d6297440b 100644 --- a/app/Events/User/Creating.php +++ b/app/Events/User/Creating.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\User; @@ -41,8 +26,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Deleted.php b/app/Events/User/Deleted.php index a3cc3cffe..1b1682ef0 100644 --- a/app/Events/User/Deleted.php +++ b/app/Events/User/Deleted.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\User; @@ -41,8 +26,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Deleting.php b/app/Events/User/Deleting.php index ad9397e45..05e3ba252 100644 --- a/app/Events/User/Deleting.php +++ b/app/Events/User/Deleting.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Events\User; @@ -41,8 +26,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Exceptions/AccountNotFoundException.php b/app/Exceptions/AccountNotFoundException.php index b35bd2fc0..f9430eeb7 100644 --- a/app/Exceptions/AccountNotFoundException.php +++ b/app/Exceptions/AccountNotFoundException.php @@ -3,28 +3,12 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Exceptions; class AccountNotFoundException extends \Exception { - // } diff --git a/app/Exceptions/AutoDeploymentException.php b/app/Exceptions/AutoDeploymentException.php index 109fe1096..d2455318f 100644 --- a/app/Exceptions/AutoDeploymentException.php +++ b/app/Exceptions/AutoDeploymentException.php @@ -3,28 +3,12 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Exceptions; class AutoDeploymentException extends \Exception { - // } diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index f3f8fd719..e3a839c5e 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -1,46 +1,77 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Exceptions; use Log; +use Throwable; +use Illuminate\Http\Response; +use Prologue\Alerts\AlertsMessageBag; -class DisplayException extends \Exception +class DisplayException extends PterodactylException { + const LEVEL_DEBUG = 'debug'; + const LEVEL_INFO = 'info'; + const LEVEL_WARNING = 'warning'; + const LEVEL_ERROR = 'error'; + + /** + * @var string + */ + protected $level; + /** * Exception constructor. * - * @param string $message - * @param mixed $log - * @return void + * @param string $message + * @param Throwable|null $previous + * @param string $level + * @param int $code */ - public function __construct($message, $log = null) + public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0) { - if (! is_null($log)) { - Log::error($log); + parent::__construct($message, $code, $previous); + + if (! is_null($previous)) { + Log::{$level}($previous); } - parent::__construct($message); + $this->level = $level; + } + + /** + * @return string + */ + public function getErrorLevel() + { + return $this->level; + } + + /** + * @return int + */ + public function getStatusCode() + { + return Response::HTTP_BAD_REQUEST; + } + + /** + * 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 + * request originated from an API hit, return the error in JSONAPI spec format. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|\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); + } + + app()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + + return redirect()->back()->withInput(); } } diff --git a/app/Exceptions/DisplayValidationException.php b/app/Exceptions/DisplayValidationException.php deleted file mode 100644 index 3d8a4fda2..000000000 --- a/app/Exceptions/DisplayValidationException.php +++ /dev/null @@ -1,30 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Exceptions; - -class DisplayValidationException extends \Exception -{ - // -} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a801cdceb..96a9c366b 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,9 +2,16 @@ namespace Pterodactyl\Exceptions; -use Log; use Exception; +use PDOException; +use Psr\Log\LoggerInterface; use Illuminate\Auth\AuthenticationException; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Validation\ValidationException; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler @@ -15,62 +22,147 @@ class Handler extends ExceptionHandler * @var array */ protected $dontReport = [ - \Illuminate\Auth\AuthenticationException::class, - \Illuminate\Auth\Access\AuthorizationException::class, - \Symfony\Component\HttpKernel\Exception\HttpException::class, - \Illuminate\Database\Eloquent\ModelNotFoundException::class, - \Illuminate\Session\TokenMismatchException::class, - \Illuminate\Validation\ValidationException::class, + AuthenticationException::class, + AuthorizationException::class, + DisplayException::class, + HttpException::class, + ModelNotFoundException::class, + RecordNotFoundException::class, + TokenMismatchException::class, + ValidationException::class, ]; /** - * Report or log an exception. + * A list of the inputs that are never flashed for validation exceptions. * - * This is a great spot to send exceptions to Sentry, Bugsnag, etc. + * @var array + */ + protected $dontFlash = [ + 'token', + 'secret', + 'password', + 'password_confirmation', + ]; + + /** + * Report or log an exception. Skips Laravel's internal reporter since we + * don't need or want the user information in our logs by default. * - * @param \Exception $exception - * @return void + * If you want to implement logging in a different format to integrate with + * services such as AWS Cloudwatch or other monitoring you can replace the + * contents of this function with a call to the parent reporter. + * + * @param \Exception $exception + * @return mixed + * + * @throws \Exception */ public function report(Exception $exception) { - return parent::report($exception); + if (! config('app.exceptions.report_all', false) && $this->shouldntReport($exception)) { + return null; + } + + if (method_exists($exception, 'report')) { + return $exception->report(); + } + + try { + $logger = $this->container->make(LoggerInterface::class); + } catch (Exception $ex) { + throw $exception; + } + + return $logger->error($exception instanceof PDOException ? $exception->getMessage() : $exception); } /** * Render an exception into an HTTP response. * - * @param \Illuminate\Http\Request $request - * @param \Exception $exception - * @return \Illuminate\Http\Response + * @param \Illuminate\Http\Request $request + * @param \Exception $exception + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Exception */ public function render($request, Exception $exception) { - if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) { - $exception = $this->prepareException($exception); + return parent::render($request, $exception); + } - if (config('app.debug') || $this->isHttpException($exception)) { - $displayError = $exception->getMessage(); - } else { - $displayError = 'An unhandled exception was encountered with this request.'; + /** + * Transform a validation exception into a consistent format to be returned for + * calls to the API. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Validation\ValidationException $exception + * @return \Illuminate\Http\JsonResponse + */ + public function invalidJson($request, ValidationException $exception) + { + $codes = collect($exception->validator->failed())->mapWithKeys(function ($reasons, $field) { + $cleaned = []; + foreach ($reasons as $reason => $attrs) { + $cleaned[] = snake_case($reason); } - $response = response()->json([ - 'error' => $displayError, - 'http_code' => (! $this->isHttpException($exception)) ?: $exception->getStatusCode(), - 'trace' => (! config('app.debug')) ? null : class_basename($exception) . ' in ' . $exception->getFile() . ' on line ' . $exception->getLine(), - ], ($this->isHttpException($exception)) ? $exception->getStatusCode() : 500, [], JSON_UNESCAPED_SLASHES); + return [str_replace('.', '_', $field) => $cleaned]; + })->toArray(); - parent::report($exception); + $errors = collect($exception->errors())->map(function ($errors, $field) use ($codes) { + $response = []; + foreach ($errors as $key => $error) { + $response[] = [ + 'code' => array_get($codes, str_replace('.', '_', $field) . '.' . $key), + 'detail' => $error, + 'source' => ['field' => $field], + ]; + } + + return $response; + })->flatMap(function ($errors) { + return $errors; + })->toArray(); + + return response()->json(['errors' => $errors], $exception->status); + } + + /** + * Return the exception as a JSONAPI representation for use on API requests. + * + * @param \Exception $exception + * @param array $override + * @return array + */ + public static function convertToArray(Exception $exception, array $override = []): array + { + $error = [ + 'code' => class_basename($exception), + 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : '500', + 'detail' => 'An error was encountered while processing this request.', + ]; + + if (config('app.debug')) { + $error = array_merge($error, [ + 'detail' => $exception->getMessage(), + 'source' => [ + 'line' => $exception->getLine(), + 'file' => str_replace(base_path(), '', $exception->getFile()), + ], + 'meta' => [ + 'trace' => explode("\n", $exception->getTraceAsString()), + ], + ]); } - return (isset($response)) ? $response : parent::render($request, $exception); + return ['errors' => [array_merge($error, $override)]]; } /** * Convert an authentication exception into an unauthenticated response. * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Auth\AuthenticationException $exception + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Auth\AuthenticationException $exception * @return \Illuminate\Http\Response */ protected function unauthenticated($request, AuthenticationException $exception) @@ -81,4 +173,16 @@ class Handler extends ExceptionHandler return redirect()->guest(route('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. + * + * @param \Exception $exception + * @return array + */ + protected function convertExceptionToArray(Exception $exception) + { + return self::convertToArray($exception); + } } diff --git a/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php new file mode 100644 index 000000000..5f17186bc --- /dev/null +++ b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Http\Base; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidPasswordProvidedException extends DisplayException +{ +} diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php new file mode 100644 index 000000000..f2892789c --- /dev/null +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -0,0 +1,45 @@ +getResponse() : null; + + if ($useStatusCode) { + $this->statusCode = is_null($response) ? 500 : $response->getStatusCode(); + } + + parent::__construct(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ]), $previous, DisplayException::LEVEL_WARNING); + } + + /** + * Return the HTTP status code for this exception. + * + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/app/Exceptions/Http/Server/FileSizeTooLargeException.php b/app/Exceptions/Http/Server/FileSizeTooLargeException.php new file mode 100644 index 000000000..c12cb7b28 --- /dev/null +++ b/app/Exceptions/Http/Server/FileSizeTooLargeException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileSizeTooLargeException extends DisplayException +{ +} diff --git a/app/Exceptions/Http/Server/FileTypeNotEditableException.php b/app/Exceptions/Http/Server/FileTypeNotEditableException.php new file mode 100644 index 000000000..c47dd3695 --- /dev/null +++ b/app/Exceptions/Http/Server/FileTypeNotEditableException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileTypeNotEditableException extends DisplayException +{ +} diff --git a/app/Exceptions/Model/DataValidationException.php b/app/Exceptions/Model/DataValidationException.php new file mode 100644 index 000000000..5840e6d9c --- /dev/null +++ b/app/Exceptions/Model/DataValidationException.php @@ -0,0 +1,60 @@ +errors()->toJson() + ); + + $this->validator = $validator; + } + + /** + * Return the validator message bag. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this->validator->errors(); + } + + /** + * Return the status code for this request. + * + * @return int + */ + public function getStatusCode() + { + return 500; + } + + /** + * @return array + */ + public function getHeaders() + { + return []; + } +} diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php new file mode 100644 index 000000000..451ae92cb --- /dev/null +++ b/app/Exceptions/PterodactylException.php @@ -0,0 +1,7 @@ +view('errors.404', [], 404); + } + } +} diff --git a/app/Exceptions/Repository/RepositoryException.php b/app/Exceptions/Repository/RepositoryException.php new file mode 100644 index 000000000..d362cd423 --- /dev/null +++ b/app/Exceptions/Repository/RepositoryException.php @@ -0,0 +1,9 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Egg; + +use Pterodactyl\Exceptions\DisplayException; + +class HasChildrenException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Egg/InvalidCopyFromException.php b/app/Exceptions/Service/Egg/InvalidCopyFromException.php new file mode 100644 index 000000000..149c42dd6 --- /dev/null +++ b/app/Exceptions/Service/Egg/InvalidCopyFromException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Egg; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidCopyFromException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php b/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php new file mode 100644 index 000000000..867b09c1a --- /dev/null +++ b/app/Exceptions/Service/Egg/NoParentConfigurationFoundException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Egg; + +use Pterodactyl\Exceptions\DisplayException; + +class NoParentConfigurationFoundException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php b/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php new file mode 100644 index 000000000..03ad09e5e --- /dev/null +++ b/app/Exceptions/Service/Egg/Variable/ReservedVariableNameException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Egg\Variable; + +use Pterodactyl\Exceptions\DisplayException; + +class ReservedVariableNameException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/HasActiveServersException.php b/app/Exceptions/Service/HasActiveServersException.php new file mode 100644 index 000000000..5c43101c5 --- /dev/null +++ b/app/Exceptions/Service/HasActiveServersException.php @@ -0,0 +1,17 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Helper; + +class CdnVersionFetchingException extends \Exception +{ +} diff --git a/app/Exceptions/Service/InvalidFileUploadException.php b/app/Exceptions/Service/InvalidFileUploadException.php new file mode 100644 index 000000000..6d0585d2a --- /dev/null +++ b/app/Exceptions/Service/InvalidFileUploadException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileUploadException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Location/HasActiveNodesException.php b/app/Exceptions/Service/Location/HasActiveNodesException.php new file mode 100644 index 000000000..1270807b8 --- /dev/null +++ b/app/Exceptions/Service/Location/HasActiveNodesException.php @@ -0,0 +1,17 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileMimeTypeException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php new file mode 100644 index 000000000..5e216fed4 --- /dev/null +++ b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidPackArchiveFormatException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php new file mode 100644 index 000000000..f1608936c --- /dev/null +++ b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class UnreadableZipArchiveException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php new file mode 100644 index 000000000..79caab261 --- /dev/null +++ b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php @@ -0,0 +1,14 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +class ZipArchiveCreationException extends \Exception +{ +} diff --git a/app/Exceptions/Service/Pack/ZipExtractionException.php b/app/Exceptions/Service/Pack/ZipExtractionException.php new file mode 100644 index 000000000..8a6a82c20 --- /dev/null +++ b/app/Exceptions/Service/Pack/ZipExtractionException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class ZipExtractionException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php b/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php new file mode 100644 index 000000000..1863ce7ee --- /dev/null +++ b/app/Exceptions/Service/Schedule/Task/TaskIntervalTooLongException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Schedule\Task; + +use Pterodactyl\Exceptions\DisplayException; + +class TaskIntervalTooLongException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Server/RequiredVariableMissingException.php b/app/Exceptions/Service/Server/RequiredVariableMissingException.php new file mode 100644 index 000000000..068c8af0f --- /dev/null +++ b/app/Exceptions/Service/Server/RequiredVariableMissingException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Server; + +use Pterodactyl\Exceptions\PterodactylException; + +class RequiredVariableMissingException extends PterodactylException +{ +} diff --git a/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php new file mode 100644 index 000000000..87c845927 --- /dev/null +++ b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Subuser; + +use Pterodactyl\Exceptions\DisplayException; + +class ServerSubuserExistsException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php new file mode 100644 index 000000000..99839424e --- /dev/null +++ b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\Subuser; + +use Pterodactyl\Exceptions\DisplayException; + +class UserIsServerOwnerException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php new file mode 100644 index 000000000..fdd0ec67f --- /dev/null +++ b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php @@ -0,0 +1,14 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Service\User; + +class TwoFactorAuthenticationTokenInvalid extends \Exception +{ +} diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php new file mode 100644 index 000000000..670e75e44 --- /dev/null +++ b/app/Extensions/DynamicDatabaseConnection.php @@ -0,0 +1,81 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Extensions; + +use Pterodactyl\Models\DatabaseHost; +use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class DynamicDatabaseConnection +{ + const DB_CHARSET = 'utf8'; + const DB_COLLATION = 'utf8_unicode_ci'; + const DB_DRIVER = 'mysql'; + + /** + * @var \Illuminate\Config\Repository + */ + protected $config; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + protected $repository; + + /** + * DynamicDatabaseConnection constructor. + * + * @param \Illuminate\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + ConfigRepository $config, + DatabaseHostRepositoryInterface $repository, + Encrypter $encrypter + ) { + $this->config = $config; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Adds a dynamic database connection entry to the runtime config. + * + * @param string $connection + * @param \Pterodactyl\Models\DatabaseHost|int $host + * @param string $database + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function set($connection, $host, $database = 'mysql') + { + if (! $host instanceof DatabaseHost) { + $host = $this->repository->find($host); + } + + $this->config->set('database.connections.' . $connection, [ + 'driver' => self::DB_DRIVER, + 'host' => $host->host, + 'port' => $host->port, + 'database' => $database, + 'username' => $host->username, + 'password' => $this->encrypter->decrypt($host->password), + 'charset' => self::DB_CHARSET, + 'collation' => self::DB_COLLATION, + ]); + } +} diff --git a/app/Extensions/Hashids.php b/app/Extensions/Hashids.php new file mode 100644 index 000000000..60e5d956b --- /dev/null +++ b/app/Extensions/Hashids.php @@ -0,0 +1,29 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Extensions; + +use Hashids\Hashids as VendorHashids; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + +class Hashids extends VendorHashids implements HashidsInterface +{ + /** + * {@inheritdoc} + */ + public function decodeFirst($encoded, $default = null) + { + $result = $this->decode($encoded); + if (! is_array($result)) { + return $default; + } + + return array_first($result, null, $default); + } +} diff --git a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php new file mode 100644 index 000000000..9599ca2c9 --- /dev/null +++ b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php @@ -0,0 +1,72 @@ + $resourceKey, + 'attributes' => $data, + ]; + } + + /** + * Serialize a collection. + * + * @param string $resourceKey + * @param array $data + * @return array + */ + public function collection($resourceKey, array $data) + { + $response = []; + foreach ($data as $datum) { + $response[] = $this->item($resourceKey, $datum); + } + + return [ + 'object' => 'list', + 'data' => $response, + ]; + } + + /** + * Serialize a null resource. + * + * @return array + */ + public function null() + { + return [ + 'object' => 'null_resource', + 'attributes' => null, + ]; + } + + /** + * Merge the included resources with the parent resource being serialized. + * + * @param array $transformedData + * @param array $includedData + * @return array + */ + public function mergeIncludes($transformedData, $includedData) + { + foreach ($includedData as $key => $datum) { + $transformedData['relationships'][$key] = $datum; + } + + return $transformedData; + } +} diff --git a/app/Extensions/PhraseAppTranslator.php b/app/Extensions/PhraseAppTranslator.php index f79cf2820..145f60392 100644 --- a/app/Extensions/PhraseAppTranslator.php +++ b/app/Extensions/PhraseAppTranslator.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Extensions; @@ -31,10 +16,10 @@ class PhraseAppTranslator extends LaravelTranslator /** * Get the translation for the given key. * - * @param string $key - * @param array $replace - * @param string|null $locale - * @param bool $fallback + * @param string $key + * @param array $replace + * @param string|null $locale + * @param bool $fallback * @return string */ public function get($key, array $replace = [], $locale = null, $fallback = true) diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php new file mode 100644 index 000000000..5bb0dd52b --- /dev/null +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -0,0 +1,46 @@ +serializer)) { + $this->serializer = new PterodactylSerializer; + } + + // Automatically set the paginator on the response object if the + // data being provided implements a paginator. + if (is_null($this->paginator) && $this->data instanceof LengthAwarePaginator) { + $this->paginator = new IlluminatePaginatorAdapter($this->data); + } + + // 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') + ) { + $this->resourceName = $this->transformer->getResourceName(); + } + + return parent::createData(); + } +} diff --git a/app/Facades/Version.php b/app/Facades/Version.php deleted file mode 100644 index e9475d2a6..000000000 --- a/app/Facades/Version.php +++ /dev/null @@ -1,40 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Facades; - -use Illuminate\Support\Facades\Facade; - -class Version extends Facade -{ - /** - * Returns the facade accessor class. - * - * @return strig - */ - protected static function getFacadeAccessor() - { - return '\Pterodactyl\Services\VersionService'; - } -} diff --git a/app/Http/Controllers/API/Admin/LocationController.php b/app/Http/Controllers/API/Admin/LocationController.php deleted file mode 100644 index 41405e91e..000000000 --- a/app/Http/Controllers/API/Admin/LocationController.php +++ /dev/null @@ -1,51 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Location; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\Admin\LocationTransformer; - -class LocationController extends Controller -{ - /** - * Controller to handle returning all locations on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('location-list', $request->apiKey()); - - return Fractal::create() - ->collection(Location::all()) - ->transformWith(new LocationTransformer($request)) - ->withResourceName('location') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/Admin/NodeController.php b/app/Http/Controllers/API/Admin/NodeController.php deleted file mode 100644 index 74784fdb6..000000000 --- a/app/Http/Controllers/API/Admin/NodeController.php +++ /dev/null @@ -1,174 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Node; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\NodeRepository; -use Pterodactyl\Transformers\Admin\NodeTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class NodeController extends Controller -{ - /** - * Controller to handle returning all nodes on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('node-list', $request->apiKey()); - - $nodes = Node::paginate(config('pterodactyl.paginate.api.nodes')); - $fractal = Fractal::create()->collection($nodes) - ->transformWith(new NodeTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($nodes)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Display information about a single node on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('node-view', $request->apiKey()); - - $fractal = Fractal::create()->item(Node::findOrFail($id)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new NodeTransformer($request)) - ->withResourceName('node') - ->toArray(); - } - - /** - * Display information about a single node on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function viewConfig(Request $request, $id) - { - $this->authorize('node-view-config', $request->apiKey()); - - $node = Node::findOrFail($id); - - return response()->json(json_decode($node->getConfigurationAsJson())); - } - - /** - * Create a new node on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('node-create', $request->apiKey()); - - $repo = new NodeRepository; - try { - $node = $repo->create(array_merge( - $request->only([ - 'public', 'disk_overallocate', 'memory_overallocate', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', - 'daemonBase', 'daemonSFTP', 'daemonListen', - ]) - )); - - $fractal = Fractal::create()->item($node)->transformWith(new NodeTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('node')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to create this node. Please try again.', - ], 500); - } - } - - /** - * Delete a node from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('node-delete', $request->apiKey()); - - $repo = new NodeRepository; - try { - $repo->delete($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to delete this node. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Admin/ServerController.php b/app/Http/Controllers/API/Admin/ServerController.php deleted file mode 100644 index 28cb40641..000000000 --- a/app/Http/Controllers/API/Admin/ServerController.php +++ /dev/null @@ -1,429 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Transformers\Admin\ServerTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class ServerController extends Controller -{ - /** - * Controller to handle returning all servers on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('server-list', $request->apiKey()); - - $servers = Server::paginate(config('pterodactyl.paginate.api.servers')); - $fractal = Fractal::create()->collection($servers) - ->transformWith(new ServerTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($servers)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Controller to handle returning information on a single server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('server-view', $request->apiKey()); - - $server = Server::findOrFail($id); - $fractal = Fractal::create()->item($server); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServerTransformer($request)) - ->withResourceName('server') - ->toArray(); - } - - /** - * Create a new server on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('server-create', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->create($request->all()); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', - ], 500); - } - } - - /** - * Delete a server from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('server-delete', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->delete($id, $request->has('force_delete')); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', - ], 500); - } - } - - /** - * Update the details for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse|array - */ - public function details(Request $request, $id) - { - $this->authorize('server-edit-details', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->updateDetails($id, $request->intersect([ - 'owner_id', 'name', 'description', 'reset_token', - ])); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify this server. Please try again.', - ], 500); - } - } - - /** - * Set the new docker container for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse|array - */ - public function container(Request $request, $id) - { - $this->authorize('server-edit-container', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->updateContainer($id, $request->intersect('docker_image')); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify this server container. Please try again.', - ], 500); - } - } - - /** - * Toggles the install status for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function install(Request $request, $id) - { - $this->authorize('server-install', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->toggleInstall($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to toggle the install status for this server. Please try again.', - ], 500); - } - } - - /** - * Setup a server to have a container rebuild. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function rebuild(Request $request, $id) - { - $this->authorize('server-rebuild', $request->apiKey()); - $server = Server::with('node')->findOrFail($id); - - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/rebuild'); - - return response('', 204); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } - } - - /** - * Manage the suspension status for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function suspend(Request $request, $id) - { - $this->authorize('server-suspend', $request->apiKey()); - - $repo = new ServerRepository; - $action = $request->input('action'); - if (! in_array($action, ['suspend', 'unsuspend'])) { - return response()->json([ - 'error' => 'The action provided was invalid. Action should be one of: suspend, unsuspend.', - ], 400); - } - - try { - $repo->toggleAccess($id, ($action === 'unsuspend')); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to ' . $action . ' this server. Please try again.', - ], 500); - } - } - - /** - * Update the build configuration for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse|array - */ - public function build(Request $request, $id) - { - $this->authorize('server-edit-build', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->changeBuild($id, $request->intersect([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', - ])); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the build settings for this server. Please try again.', - ], 500); - } - } - - /** - * Update the startup command as well as variables. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function startup(Request $request, $id) - { - $this->authorize('server-edit-startup', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->updateStartup($id, $request->all(), true); - - return response('', 204); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the startup settings for this server. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Admin/ServiceController.php b/app/Http/Controllers/API/Admin/ServiceController.php deleted file mode 100644 index fbe911f14..000000000 --- a/app/Http/Controllers/API/Admin/ServiceController.php +++ /dev/null @@ -1,74 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\Admin\ServiceTransformer; - -class ServiceController extends Controller -{ - /** - * Controller to handle returning all locations on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('service-list', $request->apiKey()); - - return Fractal::create() - ->collection(Service::all()) - ->transformWith(new ServiceTransformer($request)) - ->withResourceName('service') - ->toArray(); - } - - /** - * Controller to handle returning information on a single server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('service-view', $request->apiKey()); - - $service = Service::findOrFail($id); - $fractal = Fractal::create()->item($service); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServiceTransformer($request)) - ->withResourceName('service') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/Admin/UserController.php b/app/Http/Controllers/API/Admin/UserController.php deleted file mode 100644 index c94fe8090..000000000 --- a/app/Http/Controllers/API/Admin/UserController.php +++ /dev/null @@ -1,185 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Transformers\Admin\UserTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class UserController extends Controller -{ - /** - * Controller to handle returning all users on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('user-list', $request->apiKey()); - - $users = User::paginate(config('pterodactyl.paginate.api.users')); - $fractal = Fractal::create()->collection($users) - ->transformWith(new UserTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($users)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Display information about a single user on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('user-view', $request->apiKey()); - - $fractal = Fractal::create()->item(User::findOrFail($id)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new UserTransformer($request)) - ->withResourceName('user') - ->toArray(); - } - - /** - * Create a new user on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('user-create', $request->apiKey()); - - $repo = new UserRepository; - try { - $user = $repo->create($request->only([ - 'custom_id', 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - - $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('user')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to create this user. Please try again.', - ], 500); - } - } - - /** - * Update a user. - * - * @param \Illuminate\Http\Request $request - * @param int $user - * @return \Illuminate\Http\RedirectResponse - */ - public function update(Request $request, $user) - { - $this->authorize('user-edit', $request->apiKey()); - - $repo = new UserRepository; - try { - $user = $repo->update($user, $request->intersect([ - 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - - $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('user')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to update this user. Please try again.', - ], 500); - } - } - - /** - * Delete a user from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('user-delete', $request->apiKey()); - - $repo = new UserRepository; - try { - $repo->delete($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to delete this user. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Remote/EggInstallController.php b/app/Http/Controllers/API/Remote/EggInstallController.php new file mode 100644 index 000000000..65aedfbff --- /dev/null +++ b/app/Http/Controllers/API/Remote/EggInstallController.php @@ -0,0 +1,70 @@ +environment = $environment; + $this->repository = $repository; + } + + /** + * Handle request to get script and installation information for a server + * that is being created on the node. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request, string $uuid): JsonResponse + { + $node = $request->attributes->get('node'); + + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->repository->findFirstWhere([ + ['uuid', '=', $uuid], + ['node_id', '=', $node->id], + ]); + + $this->repository->loadEggRelations($server); + $egg = $server->getRelation('egg'); + + return response()->json([ + 'scripts' => [ + 'install' => ! $egg->copy_script_install ? null : str_replace(["\r\n", "\n", "\r"], "\n", $egg->copy_script_install), + 'privileged' => $egg->script_is_privileged, + ], + 'config' => [ + 'container' => $egg->copy_script_container, + 'entry' => $egg->copy_script_entry, + ], + 'env' => $this->environment->handle($server), + ]); + } +} diff --git a/app/Http/Controllers/API/Remote/EggRetrievalController.php b/app/Http/Controllers/API/Remote/EggRetrievalController.php new file mode 100644 index 000000000..b98a14f3f --- /dev/null +++ b/app/Http/Controllers/API/Remote/EggRetrievalController.php @@ -0,0 +1,74 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Api\Remote; + +use Illuminate\Http\JsonResponse; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\EggConfigurationService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggRetrievalController extends Controller +{ + /** + * @var \Pterodactyl\Services\Eggs\EggConfigurationService + */ + protected $configurationFileService; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * OptionUpdateController constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Services\Eggs\EggConfigurationService $configurationFileService + */ + public function __construct( + EggRepositoryInterface $repository, + EggConfigurationService $configurationFileService + ) { + $this->configurationFileService = $configurationFileService; + $this->repository = $repository; + } + + /** + * Return a JSON array of Eggs and the SHA1 hash of thier configuration file. + * + * @return \Illuminate\Http\JsonResponse + */ + public function index(): JsonResponse + { + $eggs = $this->repository->getAllWithCopyAttributes(); + + $response = []; + $eggs->each(function ($egg) use (&$response) { + $response[$egg->uuid] = sha1(json_encode($this->configurationFileService->handle($egg))); + }); + + return response()->json($response); + } + + /** + * Return the configuration file for a single Egg for the Daemon. + * + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function download(string $uuid): JsonResponse + { + $option = $this->repository->getWithCopyAttributes($uuid, 'uuid'); + + return response()->json($this->configurationFileService->handle($option)); + } +} diff --git a/app/Http/Controllers/API/Remote/SftpController.php b/app/Http/Controllers/API/Remote/SftpController.php new file mode 100644 index 000000000..083544237 --- /dev/null +++ b/app/Http/Controllers/API/Remote/SftpController.php @@ -0,0 +1,91 @@ +authenticationService = $authenticationService; + } + + /** + * Authenticate a set of credentials and return the associated server details + * for a SFTP connection on the daemon. + * + * @param \Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function index(SftpAuthenticationFormRequest $request): JsonResponse + { + $parts = explode('.', strrev($request->input('username')), 2); + $connection = [ + 'username' => strrev(array_get($parts, 1)), + 'server' => strrev(array_get($parts, 0)), + ]; + + $this->incrementLoginAttempts($request); + + if ($this->hasTooManyLoginAttempts($request)) { + return response()->json([ + 'error' => 'Logins throttled.', + ], Response::HTTP_TOO_MANY_REQUESTS); + } + + try { + $data = $this->authenticationService->handle( + $connection['username'], + $request->input('password'), + object_get($request->attributes->get('node'), 'id', 0), + empty($connection['server']) ? null : $connection['server'] + ); + + $this->clearLoginAttempts($request); + } catch (BadRequestHttpException $exception) { + return response()->json([ + 'error' => 'The server you are trying to access is not installed or is suspended.', + ], Response::HTTP_BAD_REQUEST); + } catch (RecordNotFoundException $exception) { + return response()->json([ + 'error' => 'Unable to locate a resource using the username and password provided.', + ], Response::HTTP_NOT_FOUND); + } + + return response()->json($data); + } + + /** + * Get the throttle key for the given request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function throttleKey(Request $request) + { + return strtolower(array_get(explode('.', $request->input('username')), 0) . '|' . $request->ip()); + } +} diff --git a/app/Http/Controllers/API/Remote/ValidateKeyController.php b/app/Http/Controllers/API/Remote/ValidateKeyController.php new file mode 100644 index 000000000..86a02cbce --- /dev/null +++ b/app/Http/Controllers/API/Remote/ValidateKeyController.php @@ -0,0 +1,100 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Api\Remote; + +use Spatie\Fractal\Fractal; +use Illuminate\Http\Response; +use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Foundation\Testing\HttpException; +use League\Fractal\Serializer\JsonApiSerializer; +use Pterodactyl\Transformers\Daemon\ApiKeyTransformer; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class ValidateKeyController extends Controller +{ + /** + * @var \Illuminate\Contracts\Foundation\Application + */ + protected $app; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $daemonKeyRepository; + + /** + * @var \Spatie\Fractal\Fractal + */ + protected $fractal; + + /** + * ValidateKeyController constructor. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $daemonKeyRepository + * @param \Spatie\Fractal\Fractal $fractal + */ + public function __construct( + Application $app, + DaemonKeyRepositoryInterface $daemonKeyRepository, + Fractal $fractal + ) { + $this->app = $app; + $this->daemonKeyRepository = $daemonKeyRepository; + $this->fractal = $fractal; + } + + /** + * Return the server(s) and permissions associated with an API key. + * + * @param string $token + * @return array + * + * @throws \Illuminate\Foundation\Testing\HttpException + */ + public function index($token) + { + if (! starts_with($token, DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER)) { + throw new HttpException(Response::HTTP_NOT_IMPLEMENTED); + } + + try { + $key = $this->daemonKeyRepository->getKeyWithServer($token); + } catch (RecordNotFoundException $exception) { + throw new NotFoundHttpException; + } + + if ($key->getRelation('server')->suspended || $key->getRelation('server')->installed !== 1) { + throw new NotFoundHttpException; + } + + return $this->fractal->item($key, $this->app->make(ApiKeyTransformer::class), 'server') + ->serializeWith(JsonApiSerializer::class) + ->toArray(); + } +} diff --git a/app/Http/Controllers/API/User/CoreController.php b/app/Http/Controllers/API/User/CoreController.php deleted file mode 100644 index 5c1d07406..000000000 --- a/app/Http/Controllers/API/User/CoreController.php +++ /dev/null @@ -1,51 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\User; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\User\OverviewTransformer; - -class CoreController extends Controller -{ - /** - * Controller to handle base user request for all of their servers. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('user.server-list', $request->apiKey()); - - $servers = $request->user()->access('service', 'node', 'allocation', 'option')->get(); - - return Fractal::collection($servers) - ->transformWith(new OverviewTransformer) - ->withResourceName('server') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php deleted file mode 100644 index 904935186..000000000 --- a/app/Http/Controllers/API/User/ServerController.php +++ /dev/null @@ -1,99 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\API\User; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Daemon\PowerRepository; -use Pterodactyl\Transformers\User\ServerTransformer; -use Pterodactyl\Repositories\Daemon\CommandRepository; - -class ServerController extends Controller -{ - /** - * Controller to handle base request for individual server information. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return array - */ - public function index(Request $request, $uuid) - { - $this->authorize('user.server-view', $request->apiKey()); - - $server = Server::byUuid($uuid); - $fractal = Fractal::create()->item($server); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServerTransformer) - ->withResourceName('server') - ->toArray(); - } - - /** - * Controller to handle request for server power toggle. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function power(Request $request, $uuid) - { - $this->authorize('user.server-power', $request->apiKey()); - - $server = Server::byUuid($uuid); - $request->user()->can('power-' . $request->input('action'), $server); - - $repo = new PowerRepository($server, $request->user()); - $repo->do($request->input('action')); - - return response('', 204)->header('Content-Type', 'application/json'); - } - - /** - * Controller to handle base request for individual server information. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function command(Request $request, $uuid) - { - $this->authorize('user.server-command', $request->apiKey()); - - $server = Server::byUuid($uuid); - $request->user()->can('send-command', $server); - - $repo = new CommandRepository($server, $request->user()); - $repo->send($request->input('command')); - - return response('', 204)->header('Content-Type', 'application/json'); - } -} diff --git a/app/Http/Controllers/Admin/ApiController.php b/app/Http/Controllers/Admin/ApiController.php new file mode 100644 index 000000000..bbf4bcbf8 --- /dev/null +++ b/app/Http/Controllers/Admin/ApiController.php @@ -0,0 +1,117 @@ +alert = $alert; + $this->keyCreationService = $keyCreationService; + $this->repository = $repository; + } + + /** + * Render view showing all of a user's application API keys. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request): View + { + return view('admin.api.index', [ + 'keys' => $this->repository->getApplicationKeys($request->user()), + ]); + } + + /** + * Render view allowing an admin to create a new application API key. + * + * @return \Illuminate\View\View + */ + public function create(): View + { + $resources = AdminAcl::getResourceList(); + sort($resources); + + return view('admin.api.new', [ + 'resources' => $resources, + 'permissions' => [ + 'r' => AdminAcl::READ, + 'rw' => AdminAcl::READ | AdminAcl::WRITE, + 'n' => AdminAcl::NONE, + ], + ]); + } + + /** + * Store the new key and redirect the user back to the application key listing. + * + * @param \Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreApplicationApiKeyRequest $request): RedirectResponse + { + $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([ + 'memo' => $request->input('memo'), + 'user_id' => $request->user()->id, + ], $request->getKeyPermissions()); + + $this->alert->success('A new application API key has been generated for your account.')->flash(); + + return redirect()->route('admin.api.index'); + } + + /** + * Delete an application API key from the database. + * + * @param \Illuminate\Http\Request $request + * @param string $identifier + * @return \Illuminate\Http\Response + */ + public function delete(Request $request, string $identifier): Response + { + $this->repository->deleteApplicationKey($request->user(), $identifier); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 8c5719827..b80676f2e 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -1,81 +1,35 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use Settings; -use Validator; -use Illuminate\Http\Request; +use Illuminate\View\View; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Helpers\SoftwareVersionService; class BaseController extends Controller { + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + private $version; + + /** + * BaseController constructor. + * + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $version + */ + public function __construct(SoftwareVersionService $version) + { + $this->version = $version; + } + /** * Return the admin index view. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function getIndex(Request $request) + public function index(): View { - return view('admin.index'); - } - - /** - * Return the admin settings view. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function getSettings(Request $request) - { - return view('admin.settings'); - } - - /** - * Handle settings post request. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettings(Request $request) - { - $validator = Validator::make($request->all(), [ - 'company' => 'required|between:1,256', - // 'default_language' => 'required|alpha_dash|min:2|max:5', - ]); - - if ($validator->fails()) { - return redirect()->route('admin.settings')->withErrors($validator->errors())->withInput(); - } - - Settings::set('company', $request->input('company')); - // Settings::set('default_language', $request->input('default_language')); - - Alert::success('Settings have been successfully updated.')->flash(); - - return redirect()->route('admin.settings'); + return view('admin.index', ['version' => $this->version]); } } diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 6b4e12a1d..38bc5b701 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -3,133 +3,170 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\Database; -use Pterodactyl\Models\Location; -use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Exceptions\DisplayException; +use PDOException; +use Illuminate\View\View; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\DatabaseRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Databases\Hosts\HostUpdateService; +use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; +use Pterodactyl\Services\Databases\Hosts\HostCreationService; +use Pterodactyl\Services\Databases\Hosts\HostDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; class DatabaseController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + private $alert; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService + */ + private $creationService; + + /** + * @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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService + * @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService + * @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + */ + public function __construct( + AlertsMessageBag $alert, + DatabaseHostRepositoryInterface $repository, + HostCreationService $creationService, + HostDeletionService $deletionService, + HostUpdateService $updateService, + LocationRepositoryInterface $locationRepository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->locationRepository = $locationRepository; + $this->updateService = $updateService; + } + /** * Display database host index. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index(): View { return view('admin.databases.index', [ - 'locations' => Location::with('nodes')->get(), - 'hosts' => DatabaseHost::withCount('databases')->with('node')->get(), + 'locations' => $this->locationRepository->getAllWithNodes(), + 'hosts' => $this->repository->getWithViewDetails(), ]); } /** * Display database host to user. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $host * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $id) + public function view(int $host): View { return view('admin.databases.view', [ - 'locations' => Location::with('nodes')->get(), - 'host' => DatabaseHost::with('databases.server')->findOrFail($id), + 'locations' => $this->locationRepository->getAllWithNodes(), + 'host' => $this->repository->getWithServers($host), ]); } /** - * Handle post request to create database host. + * Handle request to create a new database host. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function create(Request $request) + public function create(DatabaseHostFormRequest $request): RedirectResponse { - $repo = new DatabaseRepository; - try { - $host = $repo->add($request->intersect([ - 'name', 'username', 'password', - 'host', 'port', 'node_id', - ])); - Alert::success('Successfully created new database host on the system.')->flash(); + $host = $this->creationService->handle($request->normalize()); + } catch (PDOException $ex) { + $this->alert->danger($ex->getMessage())->flash(); - return redirect()->route('admin.databases.view', $host->id); - } catch (\PDOException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.databases')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); + return redirect()->route('admin.databases'); } - return redirect()->route('admin.databases'); + $this->alert->success('Successfully created a new database host on the system.')->flash(); + + return redirect()->route('admin.databases.view', $host->id); } /** - * Handle post request to update a database host. + * Handle updating database host. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request + * @param int $host * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(Request $request, $id) + public function update(DatabaseHostFormRequest $request, int $host): RedirectResponse { - $repo = new DatabaseRepository; - try { - if ($request->input('action') !== 'delete') { - $host = $repo->update($id, $request->intersect([ - 'name', 'username', 'password', - 'host', 'port', 'node_id', - ])); - Alert::success('Database host was updated successfully.')->flash(); - } else { - $repo->delete($id); - - return redirect()->route('admin.databases'); - } - } catch (\PDOException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.databases.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); + $host = $this->updateService->handle($host, $request->normalize()); + $this->alert->success('Database host was updated successfully.')->flash(); + } catch (PDOException $ex) { + $this->alert->danger($ex->getMessage())->flash(); } - return redirect()->route('admin.databases.view', $id); + return redirect()->route('admin.databases.view', $host->id); + } + + /** + * Handle request to delete a database host. + * + * @param int $host + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function delete(int $host): RedirectResponse + { + $this->deletionService->handle($host); + $this->alert->success('The requested database host has been deleted from the system.')->flash(); + + return redirect()->route('admin.databases'); } } diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 4e602f7ca..fbdf64003 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -3,117 +3,157 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Illuminate\Http\Request; use Pterodactyl\Models\Location; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\LocationRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Http\Requests\Admin\LocationFormRequest; +use Pterodactyl\Services\Locations\LocationUpdateService; +use Pterodactyl\Services\Locations\LocationCreationService; +use Pterodactyl\Services\Locations\LocationDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; 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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + * @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService + */ + public function __construct( + AlertsMessageBag $alert, + LocationCreationService $creationService, + LocationDeletionService $deletionService, + LocationRepositoryInterface $repository, + LocationUpdateService $updateService + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->updateService = $updateService; + } + /** * Return the location overview page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('admin.locations.index', [ - 'locations' => Location::withCount('nodes', 'servers')->get(), + 'locations' => $this->repository->getAllWithDetails(), ]); } /** * Return the location view page. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $id * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $id) + public function view($id) { - return view('admin.locations.view', ['location' => Location::with('nodes.servers')->findOrFail($id)]); + return view('admin.locations.view', [ + 'location' => $this->repository->getWithNodes($id), + ]); } /** * Handle request to create new location. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\LocationFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + * @throws \Watson\Validating\ValidationException */ - public function create(Request $request) + public function create(LocationFormRequest $request) { - $repo = new LocationRepository; + $location = $this->creationService->handle($request->normalize()); + $this->alert->success('Location was created successfully.')->flash(); - try { - $location = $repo->create($request->intersect(['short', 'long'])); - Alert::success('Location was created successfully.')->flash(); - - return redirect()->route('admin.locations.view', $location->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.locations'); + return redirect()->route('admin.locations.view', $location->id); } /** * Handle request to update or delete location. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\LocationFormRequest $request + * @param \Pterodactyl\Models\Location $location * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + * @throws \Watson\Validating\ValidationException */ - public function update(Request $request, $id) + public function update(LocationFormRequest $request, Location $location) { - $repo = new LocationRepository; - - try { - if ($request->input('action') !== 'delete') { - $location = $repo->update($id, $request->intersect(['short', 'long'])); - Alert::success('Location was updated successfully.')->flash(); - } else { - $repo->delete($id); - - return redirect()->route('admin.locations'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + if ($request->input('action') === 'delete') { + return $this->delete($location); } - return redirect()->route('admin.locations.view', $id); + $this->updateService->handle($location->id, $request->normalize()); + $this->alert->success('Location was updated successfully.')->flash(); + + return redirect()->route('admin.locations.view', $location->id); + } + + /** + * Delete a location from the system. + * + * @param \Pterodactyl\Models\Location $location + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete(Location $location) + { + try { + $this->deletionService->handle($location->id); + + return redirect()->route('admin.locations'); + } catch (DisplayException $ex) { + $this->alert->danger($ex->getMessage())->flash(); + } + + return redirect()->route('admin.locations.view', $location->id); } } diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php new file mode 100644 index 000000000..56d69e3a7 --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -0,0 +1,128 @@ +. + * + * 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 Illuminate\View\View; +use Pterodactyl\Models\Egg; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\EggUpdateService; +use Pterodactyl\Services\Eggs\EggCreationService; +use Pterodactyl\Services\Eggs\EggDeletionService; +use Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; + +class EggController extends Controller +{ + protected $alert; + protected $creationService; + protected $deletionService; + protected $nestRepository; + protected $repository; + protected $updateService; + + public function __construct( + AlertsMessageBag $alert, + EggCreationService $creationService, + EggDeletionService $deletionService, + EggRepositoryInterface $repository, + EggUpdateService $updateService, + NestRepositoryInterface $nestRepository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->nestRepository = $nestRepository; + $this->repository = $repository; + $this->updateService = $updateService; + } + + /** + * Handle a request to display the Egg creation page. + * + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function create(): View + { + $nests = $this->nestRepository->getWithEggs(); + Javascript::put(['nests' => $nests->keyBy('id')]); + + return view('admin.eggs.new', ['nests' => $nests]); + } + + /** + * Handle request to store a new Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function store(EggFormRequest $request): RedirectResponse + { + $egg = $this->creationService->handle($request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.egg_created'))->flash(); + + return redirect()->route('admin.nests.egg.view', $egg->id); + } + + /** + * Handle request to view a single Egg. + * + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\View\View + */ + public function view(Egg $egg): View + { + return view('admin.eggs.view', ['egg' => $egg]); + } + + /** + * Handle request to update an Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function update(EggFormRequest $request, Egg $egg): RedirectResponse + { + $this->updateService->handle($egg, $request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.updated'))->flash(); + + return redirect()->route('admin.nests.egg.view', $egg->id); + } + + /** + * Handle request to destroy an egg. + * + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(Egg $egg): RedirectResponse + { + $this->deletionService->handle($egg->id); + $this->alert->success(trans('admin/nests.eggs.notices.deleted'))->flash(); + + return redirect()->route('admin.nests.view', $egg->nest_id); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php new file mode 100644 index 000000000..ac67a2a6d --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -0,0 +1,98 @@ +. + * + * 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 Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest; + +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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Services\Eggs\Scripts\InstallScriptService $installScriptService + */ + public function __construct( + AlertsMessageBag $alert, + EggRepositoryInterface $repository, + InstallScriptService $installScriptService + ) { + $this->alert = $alert; + $this->installScriptService = $installScriptService; + $this->repository = $repository; + } + + /** + * Handle requests to render installation script for an Egg. + * + * @param int $egg + * @return \Illuminate\View\View + */ + public function index(int $egg): View + { + $egg = $this->repository->getWithCopyAttributes($egg); + $copy = $this->repository->findWhere([ + ['copy_script_from', '=', null], + ['nest_id', '=', $egg->nest_id], + ['id', '!=', $egg], + ]); + + $rely = $this->repository->findWhere([ + ['copy_script_from', '=', $egg->id], + ]); + + return view('admin.eggs.scripts', [ + 'copyFromOptions' => $copy, + 'relyOnScript' => $rely, + 'egg' => $egg, + ]); + } + + /** + * Handle a request to update the installation script for an Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest $request + * @param int $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException + */ + public function update(EggScriptFormRequest $request, int $egg): RedirectResponse + { + $this->installScriptService->handle($egg, $request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.script_updated'))->flash(); + + return redirect()->route('admin.nests.egg.scripts', $egg); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggShareController.php b/app/Http/Controllers/Admin/Nests/EggShareController.php new file mode 100644 index 000000000..80e8e30b9 --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggShareController.php @@ -0,0 +1,120 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Pterodactyl\Models\Egg; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Symfony\Component\HttpFoundation\Response; +use Pterodactyl\Services\Eggs\Sharing\EggExporterService; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +use Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest; +use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; + +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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService + * @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService + * @param \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService $updateImporterService + */ + public function __construct( + AlertsMessageBag $alert, + EggExporterService $exporterService, + EggImporterService $importerService, + EggUpdateImporterService $updateImporterService + ) { + $this->alert = $alert; + $this->exporterService = $exporterService; + $this->importerService = $importerService; + $this->updateImporterService = $updateImporterService; + } + + /** + * @param \Pterodactyl\Models\Egg $egg + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function export(Egg $egg): Response + { + $filename = trim(preg_replace('/[^\w]/', '-', kebab_case($egg->name)), '-'); + + return response($this->exporterService->handle($egg->id), 200, [ + 'Content-Transfer-Encoding' => 'binary', + 'Content-Description' => 'File Transfer', + 'Content-Disposition' => 'attachment; filename=egg-' . $filename . '.json', + 'Content-Type' => 'application/json', + ]); + } + + /** + * Import a new service option using an XML file. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function import(EggImportFormRequest $request): RedirectResponse + { + $egg = $this->importerService->handle($request->file('import_file'), $request->input('import_to_nest')); + $this->alert->success(trans('admin/nests.eggs.notices.imported'))->flash(); + + return redirect()->route('admin.nests.egg.view', ['egg' => $egg->id]); + } + + /** + * Update an existing Egg using a new imported file. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request + * @param int $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function update(EggImportFormRequest $request, int $egg): RedirectResponse + { + $this->updateImporterService->handle($egg, $request->file('import_file')); + $this->alert->success(trans('admin/nests.eggs.notices.updated_via_import'))->flash(); + + return redirect()->route('admin.nests.egg.view', ['egg' => $egg]); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php new file mode 100644 index 000000000..8b68743fc --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -0,0 +1,146 @@ +. + * + * 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 Pterodactyl\Models\Egg; +use Pterodactyl\Models\EggVariable; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Services\Eggs\Variables\VariableUpdateService; +use Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest; +use Pterodactyl\Services\Eggs\Variables\VariableCreationService; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; + +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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Variables\VariableCreationService $creationService + * @param \Pterodactyl\Services\Eggs\Variables\VariableUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $variableRepository + */ + public function __construct( + AlertsMessageBag $alert, + VariableCreationService $creationService, + VariableUpdateService $updateService, + EggRepositoryInterface $repository, + EggVariableRepositoryInterface $variableRepository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->repository = $repository; + $this->updateService = $updateService; + $this->variableRepository = $variableRepository; + } + + /** + * Handle request to view the variables attached to an Egg. + * + * @param int $egg + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view(int $egg): View + { + $egg = $this->repository->getWithVariables($egg); + + return view('admin.eggs.variables', ['egg' => $egg]); + } + + /** + * Handle a request to create a new Egg variable. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * + * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function store(EggVariableFormRequest $request, Egg $egg): RedirectResponse + { + $this->creationService->handle($egg->id, $request->normalize()); + $this->alert->success(trans('admin/nests.variables.notices.variable_created'))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg->id); + } + + /** + * Handle a request to update an existing Egg variable. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * @param \Pterodactyl\Models\EggVariable $variable + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function update(EggVariableFormRequest $request, Egg $egg, EggVariable $variable): RedirectResponse + { + $this->updateService->handle($variable, $request->normalize()); + $this->alert->success(trans('admin/nests.variables.notices.variable_updated', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg->id); + } + + /** + * Handle a request to delete an existing Egg variable from the Panel. + * + * @param int $egg + * @param \Pterodactyl\Models\EggVariable $variable + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(int $egg, EggVariable $variable): RedirectResponse + { + $this->variableRepository->delete($variable->id); + $this->alert->success(trans('admin/nests.variables.notices.variable_deleted', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg); + } +} diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php new file mode 100644 index 000000000..b62753cad --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -0,0 +1,160 @@ +. + * + * 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 Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest; + +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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Nests\NestCreationService $nestCreationService + * @param \Pterodactyl\Services\Nests\NestDeletionService $nestDeletionService + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + * @param \Pterodactyl\Services\Nests\NestUpdateService $nestUpdateService + */ + public function __construct( + AlertsMessageBag $alert, + NestCreationService $nestCreationService, + NestDeletionService $nestDeletionService, + NestRepositoryInterface $repository, + NestUpdateService $nestUpdateService + ) { + $this->alert = $alert; + $this->nestDeletionService = $nestDeletionService; + $this->nestCreationService = $nestCreationService; + $this->nestUpdateService = $nestUpdateService; + $this->repository = $repository; + } + + /** + * Render nest listing page. + * + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(): View + { + return view('admin.nests.index', [ + 'nests' => $this->repository->getWithCounts(), + ]); + } + + /** + * Render nest creation page. + * + * @return \Illuminate\View\View + */ + public function create(): View + { + return view('admin.nests.new'); + } + + /** + * Handle the storage of a new nest. + * + * @param \Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreNestFormRequest $request): RedirectResponse + { + $nest = $this->nestCreationService->handle($request->normalize()); + $this->alert->success(trans('admin/nests.notices.created', ['name' => $nest->name]))->flash(); + + return redirect()->route('admin.nests.view', $nest->id); + } + + /** + * Return details about a nest including all of the eggs and servers per egg. + * + * @param int $nest + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view(int $nest): View + { + return view('admin.nests.view', [ + 'nest' => $this->repository->getWithEggServers($nest), + ]); + } + + /** + * Handle request to update a nest. + * + * @param \Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest $request + * @param int $nest + * + * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(StoreNestFormRequest $request, int $nest): RedirectResponse + { + $this->nestUpdateService->handle($nest, $request->normalize()); + $this->alert->success(trans('admin/nests.notices.updated'))->flash(); + + return redirect()->route('admin.nests.view', $nest); + } + + /** + * Handle request to delete a nest. + * + * @param int $nest + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(int $nest): RedirectResponse + { + $this->nestDeletionService->handle($nest); + $this->alert->success(trans('admin/nests.notices.deleted'))->flash(); + + return redirect()->route('admin.nests'); + } +} diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index ca4836ea4..a67abf9d1 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -3,69 +3,153 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Admin; -use DB; -use Log; -use Alert; -use Cache; use Javascript; -use Pterodactyl\Models; use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Node; +use Illuminate\Http\Response; +use Pterodactyl\Models\Allocation; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\NodeRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Nodes\NodeUpdateService; +use Illuminate\Cache\Repository as CacheRepository; +use Pterodactyl\Services\Nodes\NodeCreationService; +use Pterodactyl\Services\Nodes\NodeDeletionService; +use Pterodactyl\Services\Allocations\AssignmentService; +use Pterodactyl\Services\Helpers\SoftwareVersionService; +use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest; +use Pterodactyl\Services\Allocations\AllocationDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest; class NodesController extends Controller { + /** + * @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\Services\Nodes\NodeUpdateService + */ + protected $updateService; + + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + protected $versionService; + /** + * @var \Pterodactyl\Services\Allocations\AllocationDeletionService + */ + private $allocationDeletionService; + + /** + * NodesController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Allocations\AllocationDeletionService $allocationDeletionService + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService + * @param \Illuminate\Cache\Repository $cache + * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService + * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService + */ + public function __construct( + AlertsMessageBag $alert, + AllocationDeletionService $allocationDeletionService, + AllocationRepositoryInterface $allocationRepository, + AssignmentService $assignmentService, + CacheRepository $cache, + NodeCreationService $creationService, + NodeDeletionService $deletionService, + LocationRepositoryInterface $locationRepository, + NodeRepositoryInterface $repository, + NodeUpdateService $updateService, + SoftwareVersionService $versionService + ) { + $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->updateService = $updateService; + $this->versionService = $versionService; + } + /** * Displays the index page listing all nodes on the panel. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $nodes = Models\Node::with('location')->withCount('servers'); - - if (! is_null($request->input('query'))) { - $nodes->search($request->input('query')); - } - - return view('admin.nodes.index', ['nodes' => $nodes->paginate(25)]); + return view('admin.nodes.index', [ + 'nodes' => $this->repository->setSearchTerm($request->input('query'))->getNodeListingData(), + ]); } /** * Displays create new node page. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View */ - public function create(Request $request) + public function create() { - $locations = Models\Location::all(); - if (! $locations->count()) { - Alert::warning('You must add a location before you can add a new node.')->flash(); + $locations = $this->locationRepository->all(); + if (count($locations) < 1) { + $this->alert->warning(trans('admin/node.notices.location_required'))->flash(); return redirect()->route('admin.locations'); } @@ -76,192 +160,127 @@ class NodesController extends Controller /** * Post controller to create a new node on the system. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(NodeFormRequest $request) { - try { - $repo = new NodeRepository; - $node = $repo->create(array_merge( - $request->only([ - 'public', 'disk_overallocate', - 'memory_overallocate', 'behind_proxy', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', - 'daemonBase', 'daemonSFTP', 'daemonListen', - ]) - )); - Alert::success('Successfully created new node that can be configured automatically on your remote machine by visiting the configuration tab. Before you can add any servers you need to first assign some IP addresses and ports.')->flash(); + $node = $this->creationService->handle($request->normalize()); + $this->alert->info(trans('admin/node.notices.node_created'))->flash(); - return redirect()->route('admin.nodes.view', $node->id); - } catch (DisplayValidationException $e) { - return redirect()->route('admin.nodes.new')->withErrors(json_decode($e->getMessage()))->withInput(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attempting to add this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.new')->withInput(); + return redirect()->route('admin.nodes.view.allocation', $node->id); } /** * Shows the index overview page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewIndex(Request $request, $id) + public function viewIndex(Node $node) { - $node = Models\Node::with('location')->withCount('servers')->findOrFail($id); - $stats = collect( - Models\Server::select( - DB::raw('SUM(memory) as memory, SUM(disk) as disk') - )->where('node_id', $node->id)->first() - )->mapWithKeys(function ($item, $key) use ($node) { - if ($node->{$key . '_overallocate'} > 0) { - $withover = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); - } else { - $withover = $node->{$key}; - } - - $percent = ($item / $withover) * 100; - - return [$key => [ - 'value' => number_format($item), - 'max' => number_format($withover), - 'percent' => $percent, - 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), - ]]; - })->toArray(); - - return view('admin.nodes.view.index', ['node' => $node, 'stats' => $stats]); + return view('admin.nodes.view.index', [ + 'node' => $this->repository->loadLocationAndServerCount($node), + 'stats' => $this->repository->getUsageStats($node), + 'version' => $this->versionService, + ]); } /** * Shows the settings page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View */ - public function viewSettings(Request $request, $id) + public function viewSettings(Node $node) { return view('admin.nodes.view.settings', [ - 'node' => Models\Node::findOrFail($id), - 'locations' => Models\Location::all(), + 'node' => $node, + 'locations' => $this->locationRepository->all(), ]); } /** * Shows the configuration page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View */ - public function viewConfiguration(Request $request, $id) + public function viewConfiguration(Node $node) { - return view('admin.nodes.view.configuration', [ - 'node' => Models\Node::findOrFail($id), - ]); + return view('admin.nodes.view.configuration', ['node' => $node]); } /** * Shows the allocation page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View */ - public function viewAllocation(Request $request, $id) + public function viewAllocation(Node $node) { - $node = Models\Node::findOrFail($id); - $node->setRelation('allocations', $node->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50)); + $this->repository->loadNodeAllocations($node); + Javascript::put(['node' => collect($node)->only(['id'])]); - Javascript::put([ - 'node' => collect($node)->only(['id']), - ]); - - return view('admin.nodes.view.allocation', ['node' => $node]); - } - - /** - * Shows the server listing page for a specific node. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewServers(Request $request, $id) - { - $node = Models\Node::with('servers.user', 'servers.service', 'servers.option')->findOrFail($id); - Javascript::put([ - 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), - ]); - - return view('admin.nodes.view.servers', [ + return view('admin.nodes.view.allocation', [ + 'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id), 'node' => $node, ]); } + /** + * Shows the server listing page for a specific node. + * + * @param int $node + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function viewServers($node) + { + $node = $this->repository->getNodeServers($node); + Javascript::put([ + 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), + ]); + + return view('admin.nodes.view.servers', ['node' => $node]); + } + /** * Updates settings for a node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest $request + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateSettings(Request $request, $id) + public function updateSettings(NodeFormRequest $request, Node $node) { - $repo = new NodeRepository; + $this->updateService->handle($node, $request->normalize()); + $this->alert->success(trans('admin/node.notices.node_updated'))->flash(); - try { - $node = $repo->update($id, array_merge( - $request->only([ - 'public', 'disk_overallocate', - 'memory_overallocate', 'behind_proxy', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', 'upload_size', - 'reset_secret', 'daemonSFTP', 'daemonListen', - ]) - )); - Alert::success('Successfully updated this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.nodes.view.settings', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to edit this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.view.settings', $id)->withInput(); + return redirect()->route('admin.nodes.view.settings', $node->id)->withInput(); } /** * Removes a single allocation from a node. * - * @param \Illuminate\Http\Request $request - * @param int $node - * @param int $allocation - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + * @param int $node + * @param \Pterodactyl\Models\Allocation $allocation + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException */ - public function allocationRemoveSingle(Request $request, $node, $allocation) + public function allocationRemoveSingle(int $node, Allocation $allocation): Response { - $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete(); - if ($query < 1) { - return response()->json([ - 'error' => 'Unable to find an allocation matching those details to delete.', - ], 400); - } + $this->allocationDeletionService->handle($allocation); return response('', 204); } @@ -269,18 +288,20 @@ class NodesController extends Controller /** * Remove all allocations for a specific IP at once on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node + * @param \Illuminate\Http\Request $request + * @param int $node * @return \Illuminate\Http\RedirectResponse */ public function allocationRemoveBlock(Request $request, $node) { - $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete(); - if ($query < 1) { - Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); - } else { - Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); - } + $this->allocationRepository->deleteWhere([ + ['node_id', '=', $node], + ['server_id', '=', null], + ['ip', '=', $request->input('ip')], + ]); + + $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')])) + ->flash(); return redirect()->route('admin.nodes.view.allocation', $node); } @@ -288,92 +309,64 @@ class NodesController extends Controller /** * Sets an alias for a specific allocation on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest $request + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function allocationSetAlias(Request $request, $node) + public function allocationSetAlias(AllocationAliasFormRequest $request) { - if (! $request->input('allocation_id')) { - return response('Missing required parameters.', 422); - } + $this->allocationRepository->update($request->input('allocation_id'), [ + 'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'), + ]); - try { - $update = Models\Allocation::findOrFail($request->input('allocation_id')); - $update->ip_alias = (empty($request->input('alias'))) ? null : $request->input('alias'); - $update->save(); - - return response('', 204); - } catch (\Exception $ex) { - throw $ex; - } + return response('', 204); } /** * Creates new allocations on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node + * @param \Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest $request + * @param int|\Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function createAllocation(Request $request, $node) + public function createAllocation(AllocationFormRequest $request, Node $node) { - $repo = new NodeRepository; + $this->assignmentService->handle($node, $request->normalize()); + $this->alert->success(trans('admin/node.notices.allocations_added'))->flash(); - try { - $repo->addAllocations($node, $request->intersect(['allocation_ip', 'allocation_alias', 'allocation_ports'])); - Alert::success('Successfully added new allocations!')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.nodes.view.allocation', $node)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add allocations this node. This error has been logged.')->flash(); - } - - return redirect()->route('admin.nodes.view.allocation', $node); + return redirect()->route('admin.nodes.view.allocation', $node->id); } /** * Deletes a node from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, $id) + public function delete($node) { - $repo = new NodeRepository; + $this->deletionService->handle($node); + $this->alert->success(trans('admin/node.notices.node_deleted'))->flash(); - try { - $repo->delete($id); - Alert::success('Successfully deleted the requested node from the panel.')->flash(); - - return redirect()->route('admin.nodes'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to delete this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.view', $id); + return redirect()->route('admin.nodes'); } /** * Returns the configuration token to auto-deploy a node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\Http\JsonResponse */ - public function setToken(Request $request, $id) + public function setToken(Node $node) { - $node = Models\Node::findOrFail($id); - - $token = str_random(32); - Cache::tags(['Node:Configuration'])->put($token, $node->id, 5); + $token = bin2hex(random_bytes(16)); + $this->cache->tags(['Node:Configuration'])->put($token, $node->id, 5); return response()->json(['token' => $token]); } diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php deleted file mode 100644 index 41c4544a1..000000000 --- a/app/Http/Controllers/Admin/OptionController.php +++ /dev/null @@ -1,261 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Log; -use Alert; -use Javascript; -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\OptionRepository; -use Pterodactyl\Repositories\VariableRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class OptionController extends Controller -{ - /** - * Handles request to view page for adding new option. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function create(Request $request) - { - $services = Service::with('options')->get(); - Javascript::put(['services' => $services->keyBy('id')]); - - return view('admin.services.options.new', ['services' => $services]); - } - - /** - * Handles POST request to create a new option. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Response\RedirectResponse - */ - public function store(Request $request) - { - $repo = new OptionRepository; - - try { - $option = $repo->create($request->intersect([ - 'service_id', 'name', 'description', 'tag', - 'docker_image', 'startup', 'config_from', 'config_startup', - 'config_logs', 'config_files', 'config_stop', - ])); - Alert::success('Successfully created new service option.')->flash(); - - return redirect()->route('admin.services.option.view', $option->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.new')->withInput(); - } - - /** - * Handles POST request to create a new option variable. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse - */ - public function createVariable(Request $request, $id) - { - $repo = new VariableRepository; - - try { - $variable = $repo->create($id, $request->intersect([ - 'name', 'description', 'env_variable', - 'default_value', 'options', 'rules', - ])); - - Alert::success('New variable successfully assigned to this service option.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.variables', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.variables', $id); - } - - /** - * Display option overview page. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewConfiguration(Request $request, $id) - { - return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]); - } - - /** - * Display variable overview page for a service option. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewVariables(Request $request, $id) - { - return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]); - } - - /** - * Display script management page for an option. - * - * @param Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewScripts(Request $request, $id) - { - $option = ServiceOption::with('copyFrom')->findOrFail($id); - - return view('admin.services.options.scripts', [ - 'copyFromOptions' => ServiceOption::whereNull('copy_script_from')->where([ - ['service_id', $option->service_id], - ['id', '!=', $option->id], - ])->get(), - 'relyOnScript' => ServiceOption::where('copy_script_from', $option->id)->get(), - 'option' => $option, - ]); - } - - /** - * Handles POST when editing a configration for a service option. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse - */ - public function editConfiguration(Request $request, $id) - { - $repo = new OptionRepository; - - try { - if ($request->input('action') !== 'delete') { - $repo->update($id, $request->intersect([ - 'name', 'description', 'tag', 'docker_image', 'startup', - 'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup', - ])); - Alert::success('Service option configuration has been successfully updated.')->flash(); - } else { - $option = ServiceOption::with('service')->where('id', $id)->first(); - $repo->delete($id); - Alert::success('Successfully deleted service option from the system.')->flash(); - - return redirect()->route('admin.services.view', $option->service_id); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.view', $id); - } - - /** - * Handles POST when editing a configration for a service option. - * - * @param \Illuminate\Http\Request $request - * @param int $option - * @param int $variable - * @return \Illuminate\Http\RedirectResponse - */ - public function editVariable(Request $request, $option, $variable) - { - $repo = new VariableRepository; - - try { - if ($request->input('action') !== 'delete') { - $variable = $repo->update($variable, $request->only([ - 'name', 'description', 'env_variable', - 'default_value', 'options', 'rules', - ])); - Alert::success("The service variable '{$variable->name}' has been updated.")->flash(); - } else { - $repo->delete($variable); - Alert::success('That service variable has been deleted.')->flash(); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.variables', $option); - } - - /** - * Handles POST when updating scripts for a service option. - * - * @param Request $request - * @param int $id - * @return \Illuminate\Response\RedirectResponse - */ - public function updateScripts(Request $request, $id) - { - $repo = new OptionRepository; - - try { - $repo->scripts($id, $request->only([ - 'script_install', 'script_entry', - 'script_container', 'copy_script_from', - ])); - Alert::success('Successfully updated option scripts to be run when servers are installed.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.scripts', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.scripts', $id); - } -} diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index d02273672..1dd7c881c 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -3,212 +3,248 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Storage; use Illuminate\Http\Request; use Pterodactyl\Models\Pack; -use Pterodactyl\Models\Service; -use Pterodactyl\Exceptions\DisplayException; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\PackRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Packs\ExportPackService; +use Pterodactyl\Services\Packs\PackUpdateService; +use Pterodactyl\Services\Packs\PackCreationService; +use Pterodactyl\Services\Packs\PackDeletionService; +use Pterodactyl\Http\Requests\Admin\PackFormRequest; +use Pterodactyl\Services\Packs\TemplateUploadService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; class PackController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Packs\PackDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Services\Packs\ExportPackService + */ + protected $exportService; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Packs\PackUpdateService + */ + protected $updateService; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $serviceRepository; + + /** + * @var \Pterodactyl\Services\Packs\TemplateUploadService + */ + protected $templateUploadService; + + /** + * PackController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Packs\ExportPackService $exportService + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Services\Packs\PackUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $serviceRepository + * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService + */ + public function __construct( + AlertsMessageBag $alert, + ConfigRepository $config, + ExportPackService $exportService, + PackCreationService $creationService, + PackDeletionService $deletionService, + PackRepositoryInterface $repository, + PackUpdateService $updateService, + NestRepositoryInterface $serviceRepository, + TemplateUploadService $templateUploadService + ) { + $this->alert = $alert; + $this->config = $config; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->exportService = $exportService; + $this->repository = $repository; + $this->updateService = $updateService; + $this->serviceRepository = $serviceRepository; + $this->templateUploadService = $templateUploadService; + } + /** * Display listing of all packs on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $packs = Pack::with('option')->withCount('servers'); - - if (! is_null($request->input('query'))) { - $packs->search($request->input('query')); - } - - return view('admin.packs.index', ['packs' => $packs->paginate(50)]); + return view('admin.packs.index', [ + 'packs' => $this->repository->setSearchTerm($request->input('query'))->paginateWithEggAndServerCount(), + ]); } /** * Display new pack creation form. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function create(Request $request) + public function create() { return view('admin.packs.new', [ - 'services' => Service::with('options')->get(), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } /** * Display new pack creation modal for use with template upload. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function newTemplate(Request $request) + public function newTemplate() { return view('admin.packs.modal', [ - 'services' => Service::with('options')->get(), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } /** * Handle create pack request and route user to location. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException */ - public function store(Request $request) + public function store(PackFormRequest $request) { - $repo = new PackRepository; - - try { - if ($request->input('action') === 'from_template') { - $pack = $repo->createWithTemplate($request->intersect(['option_id', 'file_upload'])); - } else { - $pack = $repo->create($request->intersect([ - 'name', 'description', 'version', 'option_id', - 'selectable', 'visible', 'locked', 'file_upload', - ])); - } - Alert::success('Pack successfully created on the system.')->flash(); - - return redirect()->route('admin.packs.view', $pack->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.packs.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new service pack. This error has been logged.')->flash(); + if ($request->filled('from_template')) { + $pack = $this->templateUploadService->handle($request->input('egg_id'), $request->file('file_upload')); + } else { + $pack = $this->creationService->handle($request->normalize(), $request->file('file_upload')); } - return redirect()->route('admin.packs.new')->withInput(); + $this->alert->success(trans('admin/pack.notices.pack_created'))->flash(); + + return redirect()->route('admin.packs.view', $pack->id); } /** * Display pack view template to user. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Pack $pack * @return \Illuminate\View\View + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $id) + public function view(Pack $pack) { return view('admin.packs.view', [ - 'pack' => Pack::with('servers.node', 'servers.user')->findOrFail($id), - 'services' => Service::with('options')->get(), + 'pack' => $this->repository->loadServerData($pack), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } /** * Handle updating or deleting pack information. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request + * @param \Pterodactyl\Models\Pack $pack * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function update(Request $request, $id) + public function update(PackFormRequest $request, Pack $pack) { - $repo = new PackRepository; + $this->updateService->handle($pack, $request->normalize()); + $this->alert->success(trans('admin/pack.notices.pack_updated'))->flash(); - try { - if ($request->input('action') !== 'delete') { - $pack = $repo->update($id, $request->intersect([ - 'name', 'description', 'version', - 'option_id', 'selectable', 'visible', 'locked', - ])); - Alert::success('Pack successfully updated.')->flash(); - } else { - $repo->delete($id); - Alert::success('Pack was successfully deleted from the system.')->flash(); + return redirect()->route('admin.packs.view', $pack->id); + } - return redirect()->route('admin.packs'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.packs.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to edit this service pack. This error has been logged.')->flash(); - } + /** + * Delete a pack if no servers are attached to it currently. + * + * @param \Pterodactyl\Models\Pack $pack + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(Pack $pack) + { + $this->deletionService->handle($pack->id); + $this->alert->success(trans('admin/pack.notices.pack_deleted', [ + 'name' => $pack->name, + ]))->flash(); - return redirect()->route('admin.packs.view', $id); + return redirect()->route('admin.packs'); } /** * Creates an archive of the pack and downloads it to the browser. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @param bool $files - * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * @param \Pterodactyl\Models\Pack $pack + * @param bool|string $files + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException */ - public function export(Request $request, $id, $files = false) + public function export(Pack $pack, $files = false) { - $pack = Pack::findOrFail($id); - $json = [ - 'name' => $pack->name, - 'version' => $pack->version, - 'description' => $pack->description, - 'selectable' => $pack->selectable, - 'visible' => $pack->visible, - 'locked' => $pack->locked, - ]; - - $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); - if ($files === 'with-files') { - $zip = new \ZipArchive; - if (! $zip->open($filename, \ZipArchive::CREATE)) { - abort(503, 'Unable to open file for writing.'); - } - - $files = Storage::files('packs/' . $pack->uuid); - foreach ($files as $file) { - $zip->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); - } - - $zip->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); - $zip->close(); + $filename = $this->exportService->handle($pack, is_string($files)); + if (is_string($files)) { return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true); - } else { - $fp = fopen($filename, 'a+'); - fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); - fclose($fp); - - return response()->download($filename, 'pack-' . $pack->name . '.json', [ - 'Content-Type' => 'application/json', - ])->deleteFileAfterSend(true); } + + return response()->download($filename, 'pack-' . $pack->name . '.json', [ + 'Content-Type' => 'application/json', + ])->deleteFileAfterSend(true); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 76715c84c..c64e76711 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -3,237 +3,368 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; use Javascript; -use Pterodactyl\Models; use Illuminate\Http\Request; -use GuzzleHttp\Exception\TransferException; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Repositories\DatabaseRepository; -use Pterodactyl\Exceptions\AutoDeploymentException; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Servers\SuspensionService; +use Pterodactyl\Http\Requests\Admin\ServerFormRequest; +use Pterodactyl\Services\Servers\ServerCreationService; +use Pterodactyl\Services\Servers\ServerDeletionService; +use Pterodactyl\Services\Servers\ReinstallServerService; +use Pterodactyl\Services\Servers\ContainerRebuildService; +use Pterodactyl\Services\Servers\BuildModificationService; +use Pterodactyl\Services\Databases\DatabasePasswordService; +use Pterodactyl\Services\Servers\DetailsModificationService; +use Pterodactyl\Services\Servers\StartupModificationService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Pterodactyl\Services\Databases\DatabaseManagementService; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; 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\Services\Servers\ContainerRebuildService + */ + protected $containerRebuildService; + + /** + * @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\Contracts\Repository\LocationRepositoryInterface + */ + protected $locationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $nestRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * @var \Pterodactyl\Services\Servers\ReinstallServerService + */ + protected $reinstallService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Servers\ServerCreationService + */ + protected $service; + + /** + * @var \Pterodactyl\Services\Servers\StartupModificationService + */ + private $startupModificationService; + + /** + * @var \Pterodactyl\Services\Servers\SuspensionService + */ + protected $suspensionService; + + /** + * ServersController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService + * @param \Pterodactyl\Services\Servers\ServerCreationService $service + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository + * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService + * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository + * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService + * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService + */ + public function __construct( + AlertsMessageBag $alert, + AllocationRepositoryInterface $allocationRepository, + BuildModificationService $buildModificationService, + ConfigRepository $config, + ContainerRebuildService $containerRebuildService, + ServerCreationService $service, + DatabaseManagementService $databaseManagementService, + DatabasePasswordService $databasePasswordService, + DatabaseRepositoryInterface $databaseRepository, + DatabaseHostRepository $databaseHostRepository, + ServerDeletionService $deletionService, + DetailsModificationService $detailsModificationService, + LocationRepositoryInterface $locationRepository, + NodeRepositoryInterface $nodeRepository, + ReinstallServerService $reinstallService, + ServerRepositoryInterface $repository, + NestRepositoryInterface $nestRepository, + StartupModificationService $startupModificationService, + SuspensionService $suspensionService + ) { + $this->alert = $alert; + $this->allocationRepository = $allocationRepository; + $this->buildModificationService = $buildModificationService; + $this->config = $config; + $this->containerRebuildService = $containerRebuildService; + $this->databaseHostRepository = $databaseHostRepository; + $this->databaseManagementService = $databaseManagementService; + $this->databasePasswordService = $databasePasswordService; + $this->databaseRepository = $databaseRepository; + $this->detailsModificationService = $detailsModificationService; + $this->deletionService = $deletionService; + $this->locationRepository = $locationRepository; + $this->nestRepository = $nestRepository; + $this->nodeRepository = $nodeRepository; + $this->reinstallService = $reinstallService; + $this->repository = $repository; + $this->service = $service; + $this->startupModificationService = $startupModificationService; + $this->suspensionService = $suspensionService; + } + /** * Display the index page with all servers currently on the system. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { - $servers = Models\Server::with('node', 'user', 'allocation'); - - if (! is_null($request->input('query'))) { - $servers->search($request->input('query')); - } - return view('admin.servers.index', [ - 'servers' => $servers->paginate(25), + 'servers' => $this->repository->getAllServers( + $this->config->get('pterodactyl.paginate.admin.servers') + ), ]); } /** * Display create new server page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Exception */ - public function create(Request $request) + public function create() { - $services = Models\Service::with('options.packs', 'options.variables')->get(); + $nodes = $this->nodeRepository->all(); + if (count($nodes) < 1) { + $this->alert->warning(trans('admin/server.alerts.node_required'))->flash(); + + return redirect()->route('admin.nodes'); + } + + $nests = $this->nestRepository->getWithEggs(); + Javascript::put([ - 'services' => $services->map(function ($item) { + 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), + 'nests' => $nests->map(function ($item) { return array_merge($item->toArray(), [ - 'options' => $item->options->keyBy('id')->toArray(), + 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); })->keyBy('id'), ]); return view('admin.servers.new', [ - 'locations' => Models\Location::all(), - 'services' => $services, + 'locations' => $this->locationRepository->all(), + 'nests' => $nests, ]); } /** - * Create server controller method. + * Handle POST of server creation form. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Response\RedirectResponse - */ - public function store(Request $request) - { - try { - $repo = new ServerRepository; - $server = $repo->create($request->except('_token')); - - return redirect()->route('admin.servers.view', $server->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (AutoDeploymentException $ex) { - Alert::danger('Auto-Deployment Exception: ' . $ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); - } - - return redirect()->route('admin.servers.new')->withInput(); - } - - /** - * Returns a tree of all avaliable nodes in a given location. + * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request + * @return \Illuminate\Http\RedirectResponse * - * @param \Illuminate\Http\Request $request - * @return array + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ - public function nodes(Request $request) + public function store(ServerFormRequest $request) { - $nodes = Models\Node::with('allocations')->where('location_id', $request->input('location'))->get(); + $server = $this->service->handle($request->except('_token')); + $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - return $nodes->map(function ($item) { - $filtered = $item->allocations->where('server_id', null)->map(function ($map) { - return collect($map)->only(['id', 'ip', 'port']); - }); - - $item->ports = $filtered->map(function ($map) use ($item) { - return [ - 'id' => $map['id'], - 'text' => $map['ip'] . ':' . $map['port'], - ]; - })->values(); - - return [ - 'id' => $item->id, - 'text' => $item->name, - 'allocations' => $item->ports, - ]; - })->values(); + return redirect()->route('admin.servers.view', $server->id); } /** * Display the index when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewIndex(Request $request, $id) + public function viewIndex(Server $server) { - return view('admin.servers.view.index', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.index', ['server' => $server]); } /** * Display the details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewDetails(Request $request, $id) + public function viewDetails($server) { - $server = Models\Server::where('installed', 1)->findOrFail($id); - - return view('admin.servers.view.details', ['server' => $server]); + return view('admin.servers.view.details', [ + 'server' => $this->repository->findFirstWhere([ + ['id', '=', $server], + ['installed', '=', 1], + ]), + ]); } /** * Display the build details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewBuild(Request $request, $id) + public function viewBuild($server) { - $server = Models\Server::where('installed', 1)->with('node.allocations')->findOrFail($id); + $server = $this->repository->findFirstWhere([ + ['id', '=', $server], + ['installed', '=', 1], + ]); + + $allocations = $this->allocationRepository->getAllocationsForNode($server->node_id); return view('admin.servers.view.build', [ 'server' => $server, - 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), - 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), + 'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), + 'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), ]); } /** * Display startup configuration page for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewStartup(Request $request, $id) + public function viewStartup(Server $server) { - $server = Models\Server::where('installed', 1)->with('option.variables', 'variables')->findOrFail($id); - $server->option->variables->transform(function ($item, $key) use ($server) { - $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + $parameters = $this->repository->getVariablesWithValues($server->id, true); + if (! $parameters->server->installed) { + abort(404); + } - return $item; - }); + $nests = $this->nestRepository->getWithEggs(); - $services = Models\Service::with('options.packs', 'options.variables')->get(); Javascript::put([ - 'services' => $services->map(function ($item) { + 'server' => $server, + 'nests' => $nests->map(function ($item) { return array_merge($item->toArray(), [ - 'options' => $item->options->keyBy('id')->toArray(), + 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); })->keyBy('id'), - 'server_variables' => $server->variables->mapWithKeys(function ($item) { - return ['env_' . $item->variable_id => [ - 'value' => $item->variable_value, - ]]; - })->toArray(), + 'server_variables' => $parameters->data, ]); return view('admin.servers.view.startup', [ - 'server' => $server, - 'services' => $services, + 'server' => $parameters->server, + 'nests' => $nests, ]); } /** * Display the database management page for a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewDatabase(Request $request, $id) + public function viewDatabase(Server $server) { - $server = Models\Server::where('installed', 1)->with('databases.host')->findOrFail($id); + $this->repository->loadDatabaseRelations($server); return view('admin.servers.view.database', [ - 'hosts' => Models\DatabaseHost::all(), + 'hosts' => $this->databaseHostRepository->all(), 'server' => $server, ]); } @@ -241,360 +372,247 @@ class ServersController extends Controller /** * Display the management page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewManage(Request $request, $id) + public function viewManage(Server $server) { - return view('admin.servers.view.manage', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.manage', ['server' => $server]); } /** * Display the deletion page for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewDelete(Request $request, $id) + public function viewDelete(Server $server) { - return view('admin.servers.view.delete', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.delete', ['server' => $server]); } /** * Update the details for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse - */ - public function setDetails(Request $request, $id) - { - $repo = new ServerRepository; - try { - $repo->updateDetails($id, array_merge( - $request->only('description'), - $request->intersect([ - 'owner_id', 'name', 'reset_token', - ]) - )); - - Alert::success('Server details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.details', $id)->withInput(); - } - - /** - * Set the new docker container for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function setContainer(Request $request, $id) + public function setDetails(Request $request, Server $server) { - $repo = new ServerRepository; + $this->detailsModificationService->handle($server, $request->only([ + 'owner_id', 'name', 'description', + ])); - try { - $repo->updateContainer($id, $request->intersect('docker_image')); + $this->alert->success(trans('admin/server.alerts.details_updated'))->flash(); - Alert::success('Successfully updated this server\'s docker image.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occured while attempting to update the container image. Is the daemon online? This error has been logged.'); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.details', $id); + return redirect()->route('admin.servers.view.details', $server->id); } /** * Toggles the install status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function toggleInstall(Request $request, $id) + public function toggleInstall(Server $server) { - $repo = new ServerRepository; - try { - $repo->toggleInstall($id); - - Alert::success('Server install status was successfully toggled.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to toggle this servers status. This error has been logged.')->flash(); + if ($server->installed > 1) { + throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); } - return redirect()->route('admin.servers.view.manage', $id); + $this->repository->update($server->id, [ + 'installed' => ! $server->installed, + ], true, true); + + $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash(); + + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Reinstalls the server with the currently assigned pack and service. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function reinstallServer(Request $request, $id) + public function reinstallServer(Server $server) { - $repo = new ServerRepository; - try { - $repo->reinstall($id); + $this->reinstallService->reinstall($server); + $this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash(); - Alert::success('Server successfully marked for reinstallation.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to perform this reinstallation. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Setup a server to have a container rebuild. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse */ - public function rebuildContainer(Request $request, $id) + public function rebuildContainer(Server $server) { - $server = Models\Server::with('node')->findOrFail($id); + $this->containerRebuildService->handle($server); + $this->alert->success(trans('admin/server.alerts.rebuild_on_boot'))->flash(); - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/rebuild'); - - Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Manage the suspension status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function manageSuspension(Request $request, $id) + public function manageSuspension(Request $request, Server $server) { - $repo = new ServerRepository; - $action = $request->input('action'); + $this->suspensionService->toggle($server, $request->input('action')); + $this->alert->success(trans('admin/server.alerts.suspension_toggled', [ + 'status' => $request->input('action') . 'ed', + ]))->flash(); - if (! in_array($action, ['suspend', 'unsuspend'])) { - Alert::danger('Invalid action was passed to function.')->flash(); - - return redirect()->route('admin.servers.view.manage', $id); - } - - try { - $repo->toggleAccess($id, ($action === 'unsuspend')); - - Alert::success('Server has been ' . $action . 'ed.'); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to ' . $action . ' this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Update the build configuration for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @internal param int $id */ - public function updateBuild(Request $request, $id) + public function updateBuild(Request $request, Server $server) { - $repo = new ServerRepository; + $this->buildModificationService->handle($server, $request->only([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', 'disk', + ])); + $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); - try { - $repo->changeBuild($id, $request->intersect([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'disk', - ])); - - Alert::success('Server details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.build', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.build', $id); + return redirect()->route('admin.servers.view.build', $server->id); } /** * Start the server deletion process. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(Request $request, $id) + public function delete(Request $request, Server $server) { - $repo = new ServerRepository; + $this->deletionService->withForce($request->filled('force_delete'))->handle($server); + $this->alert->success(trans('admin/server.alerts.server_deleted'))->flash(); - try { - $repo->delete($id, $request->has('force_delete')); - Alert::success('Server was successfully deleted from the system.')->flash(); - - return redirect()->route('admin.servers'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to delete this server from the daemon, please ensure it is running. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.delete', $id); + return redirect()->route('admin.servers'); } /** * Update the startup command as well as variables. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function saveStartup(Request $request, $id) + public function saveStartup(Request $request, Server $server) { - $repo = new ServerRepository; + $this->startupModificationService->setUserLevel(User::USER_LEVEL_ADMIN); + $this->startupModificationService->handle($server, $request->except('_token')); + $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); - try { - if ($repo->updateStartup($id, $request->except('_token'), true)) { - Alert::success('Service configuration successfully modfied for this server, reinstalling now.')->flash(); - - return redirect()->route('admin.servers.view', $id); - } else { - Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to update the startup for this server, please ensure the daemon is running. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.startup', $id); + return redirect()->route('admin.servers.view.startup', $server->id); } /** * Creates a new database assigned to a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function newDatabase(Request $request, $id) + public function newDatabase(Request $request, $server) { - $repo = new DatabaseRepository; + $this->databaseManagementService->create($server, [ + 'database' => $request->input('database'), + 'remote' => $request->input('remote'), + 'database_host_id' => $request->input('database_host_id'), + ]); - try { - $repo->create($id, $request->only(['host', 'database', 'connection'])); - - Alert::success('A new database was assigned to this server successfully.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.database', $id)->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An exception occured while attempting to add a new database for this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.database', $id)->withInput(); + return redirect()->route('admin.servers.view.database', $server)->withInput(); } /** * Resets the database password for a specific database on this server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function resetDatabasePassword(Request $request, $id) + public function resetDatabasePassword(Request $request, $server) { - $database = Models\Database::where('server_id', $id)->findOrFail($request->input('database')); - $repo = new DatabaseRepository; + $database = $this->databaseRepository->findFirstWhere([ + ['server_id', '=', $server], + ['id', '=', $request->input('database')], + ]); - try { - $repo->password($database->id, str_random(20)); + $this->databasePasswordService->handle($database, str_random(20)); - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json(['error' => 'A unhandled exception occurred while attempting to reset this password. This error has been logged.'], 503); - } + return response('', 204); } /** * Deletes a database from a server. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @param int $database + * @param int $server + * @param int $database * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function deleteDatabase(Request $request, $id, $database) + public function deleteDatabase($server, $database) { - $database = Models\Database::where('server_id', $id)->findOrFail($database); - $repo = new DatabaseRepository; + $database = $this->databaseRepository->findFirstWhere([ + ['server_id', '=', $server], + ['id', '=', $database], + ]); - try { - $repo->drop($database->id); + $this->databaseManagementService->delete($database->id); - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json(['error' => 'A unhandled exception occurred while attempting to drop this database. This error has been logged.'], 503); - } + return response('', 204); } } diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php deleted file mode 100644 index beaac5340..000000000 --- a/app/Http/Controllers/Admin/ServiceController.php +++ /dev/null @@ -1,152 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Log; -use Alert; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServiceRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServiceController extends Controller -{ - /** - * Display service overview page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - return view('admin.services.index', [ - 'services' => Models\Service::withCount('servers', 'options', 'packs')->get(), - ]); - } - - /** - * Display create service page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function create(Request $request) - { - return view('admin.services.new'); - } - - /** - * Return base view for a service. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function view(Request $request, $id) - { - return view('admin.services.view', [ - 'service' => Models\Service::with('options', 'options.servers')->findOrFail($id), - ]); - } - - /** - * Return function editing view for a service. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewFunctions(Request $request, $id) - { - return view('admin.services.functions', ['service' => Models\Service::findOrFail($id)]); - } - - /** - * Handle post action for new service. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function store(Request $request) - { - $repo = new ServiceRepository; - - try { - $service = $repo->create($request->intersect([ - 'name', 'description', 'folder', 'startup', - ])); - Alert::success('Successfully created new service!')->flash(); - - return redirect()->route('admin.services.view', $service->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new service. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.new')->withInput(); - } - - /** - * Edits configuration for a specific service. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse - */ - public function edit(Request $request, $id) - { - $repo = new ServiceRepository; - $redirectTo = ($request->input('redirect_to')) ? 'admin.services.view.functions' : 'admin.services.view'; - - try { - if ($request->input('action') !== 'delete') { - $repo->update($id, $request->intersect([ - 'name', 'description', 'folder', 'startup', 'index_file', - ])); - Alert::success('Service has been updated successfully.')->flash(); - } else { - $repo->delete($id); - Alert::success('Successfully deleted service from the system.')->flash(); - - return redirect()->route('admin.services'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route($redirectTo, $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occurred while attempting to update this service. This error has been logged.')->flash(); - } - - return redirect()->route($redirectTo, $id); - } -} diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php new file mode 100644 index 000000000..f8249acd3 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -0,0 +1,91 @@ +alert = $alert; + $this->config = $config; + $this->kernel = $kernel; + $this->settings = $settings; + } + + /** + * Render advanced Panel settings UI. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + $showRecaptchaWarning = false; + if ( + $this->config->get('recaptcha._shipped_secret_key') === $this->config->get('recaptcha.secret_key') + || $this->config->get('recaptcha._shipped_website_key') === $this->config->get('recaptcha.website_key') + ) { + $showRecaptchaWarning = true; + } + + return view('admin.settings.advanced', [ + 'showRecaptchaWarning' => $showRecaptchaWarning, + ]); + } + + /** + * @param \Pterodactyl\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function update(AdvancedSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings.advanced'); + } +} diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php new file mode 100644 index 000000000..604684da4 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -0,0 +1,89 @@ +alert = $alert; + $this->kernel = $kernel; + $this->settings = $settings; + $this->versionService = $versionService; + } + + /** + * Render the UI for basic Panel settings. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + return view('admin.settings.index', [ + 'version' => $this->versionService, + 'languages' => $this->getAvailableLanguages(true), + ]); + } + + /** + * Handle settings update. + * + * @param \Pterodactyl\Http\Requests\Admin\Settings\BaseSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function update(BaseSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings'); + } +} diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php new file mode 100644 index 000000000..0e5a1d737 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -0,0 +1,112 @@ +alert = $alert; + $this->config = $config; + $this->encrypter = $encrypter; + $this->kernel = $kernel; + $this->settings = $settings; + } + + /** + * Render UI for editing mail settings. This UI should only display if + * the server is configured to send mail using SMTP. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + return view('admin.settings.mail', [ + 'disabled' => $this->config->get('mail.driver') !== 'smtp', + ]); + } + + /** + * Handle request to update SMTP mail settings. + * + * @param \Pterodactyl\Http\Requests\Admin\Settings\MailSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function update(MailSettingsFormRequest $request): RedirectResponse + { + if ($this->config->get('mail.driver') !== 'smtp') { + throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.'); + } + + $values = $request->normalize(); + if (array_get($values, 'mail:password') === '!e') { + $values['mail:password'] = ''; + } + + foreach ($values as $key => $value) { + if (in_array($key, SettingsServiceProvider::getEncryptedKeys()) && ! empty($value)) { + $value = $this->encrypter->encrypt($value); + } + + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Mail settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings.mail'); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 0e943eb46..e0474fa5b 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,180 +1,202 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Services\Users\UserUpdateService; +use Pterodactyl\Traits\Helpers\AvailableLanguages; +use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Services\Users\UserDeletionService; +use Pterodactyl\Http\Requests\Admin\UserFormRequest; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; 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. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + AlertsMessageBag $alert, + UserCreationService $creationService, + UserDeletionService $deletionService, + Translator $translator, + UserUpdateService $updateService, + UserRepositoryInterface $repository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->translator = $translator; + $this->updateService = $updateService; + } + /** * Display user index page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $users = User::withCount('servers', 'subuserOf'); + $users = $this->repository->setSearchTerm($request->input('query'))->getAllUsersWithCounts(); - if (! is_null($request->input('query'))) { - $users->search($request->input('query')); - } - - return view('admin.users.index', [ - 'users' => $users->paginate(25), - ]); + return view('admin.users.index', ['users' => $users]); } /** * Display new user page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create() { - return view('admin.users.new'); + return view('admin.users.new', [ + 'languages' => $this->getAvailableLanguages(true), + ]); } /** * Display user view page. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\User $user * @return \Illuminate\View\View */ - public function view(Request $request, $id) + public function view(User $user) { return view('admin.users.view', [ - 'user' => User::with('servers.node')->findOrFail($id), + 'user' => $user, + 'languages' => $this->getAvailableLanguages(true), ]); } /** - * Delete a user. + * Delete a user from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\User $user * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, $id) + public function delete(Request $request, User $user) { - try { - $repo = new UserRepository; - $repo->delete($id); - Alert::success('Successfully deleted user from system.')->flash(); - - return redirect()->route('admin.users'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An exception was encountered while attempting to delete this user.')->flash(); + if ($request->user()->id === $user->id) { + throw new DisplayException($this->translator->trans('admin/user.exceptions.user_has_servers')); } - return redirect()->route('admin.users.view', $id); + $this->deletionService->handle($user); + + return redirect()->route('admin.users'); } /** * Create a user. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\UserFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Throwable */ - public function store(Request $request) + public function store(UserFormRequest $request) { - try { - $user = new UserRepository; - $userid = $user->create($request->only([ - 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - Alert::success('Account has been successfully created.')->flash(); + $user = $this->creationService->handle($request->normalize()); + $this->alert->success($this->translator->trans('admin/user.notices.account_created'))->flash(); - return redirect()->route('admin.users.view', $userid); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new user.')->flash(); - - return redirect()->route('admin.users.new'); - } + return redirect()->route('admin.users.view', $user->id); } /** - * Update a user. + * Update a user on the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\UserFormRequest $request + * @param \Pterodactyl\Models\User $user * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(Request $request, $id) + public function update(UserFormRequest $request, User $user) { - try { - $repo = new UserRepository; - $user = $repo->update($id, array_merge( - $request->only('root_admin'), - $request->intersect([ - 'email', 'password', 'name_first', - 'name_last', 'username', - ]) - )); - Alert::success('User account was successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.users.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to update this user.')->flash(); + $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); + $data = $this->updateService->handle($user, $request->normalize()); + + if (! empty($data->get('exceptions'))) { + foreach ($data->get('exceptions') as $node => $exception) { + /** @var \GuzzleHttp\Exception\RequestException $exception */ + /** @var \GuzzleHttp\Psr7\Response|null $response */ + $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; + $message = trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ]); + + $this->alert->danger(trans('exceptions.users.node_revocation_failed', [ + 'node' => $node, + 'error' => $message, + 'link' => route('admin.nodes.view', $node), + ]))->flash(); + } } - return redirect()->route('admin.users.view', $id); + $this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash(); + + return redirect()->route('admin.users.view', $user->id); } /** * Get a JSON response of users on the system. * - * @param \Illuminate\Http\Request $request - * @return \Pterodactyl\Models\User + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Database\Eloquent\Collection */ public function json(Request $request) { - return User::select('id', 'email', 'username', 'name_first', 'name_last') - ->search($request->input('q')) - ->get()->transform(function ($item) { - $item->md5 = md5(strtolower($item->email)); - - return $item; - }); + return $this->repository->filterUsersByQuery($request->input('q')); } } diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php new file mode 100644 index 000000000..c932c3644 --- /dev/null +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -0,0 +1,76 @@ +call([$this, 'loadDependencies']); + + // Parse all of the includes to use on this request. + $includes = collect(explode(',', $this->request->input('include', '')))->map(function ($value) { + return trim($value); + })->filter()->toArray(); + + $this->fractal->parseIncludes($includes); + $this->fractal->limitRecursion(2); + } + + /** + * Perform dependency injection of certain classes needed for core functionality + * without littering the constructors of classes that extend this abstract. + * + * @param \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal $fractal + * @param \Illuminate\Http\Request $request + */ + public function loadDependencies(Fractal $fractal, Request $request) + { + $this->fractal = $fractal; + $this->request = $request; + } + + /** + * Return an instance of an application transformer. + * + * @param string $abstract + * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + */ + 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')); + + return $transformer; + } + + /** + * Return a HTTP/204 response for the API. + * + * @return \Illuminate\Http\Response + */ + protected function returnNoContent(): Response + { + return new Response('', Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php new file mode 100644 index 000000000..6520b5918 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -0,0 +1,145 @@ +creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->updateService = $updateService; + } + + /** + * Return all of the locations currently registered on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest $request + * @return array + */ + public function index(GetLocationsRequest $request): array + { + $locations = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + + return $this->fractal->collection($locations) + ->transformWith($this->getTransformer(LocationTransformer::class)) + ->toArray(); + } + + /** + * Return a single location. + * + * @param \Pterodactyl\Http\Controllers\Api\Application\Locations\GetLocationRequest $request + * @return array + */ + public function view(GetLocationRequest $request): array + { + return $this->fractal->item($request->getModel(Location::class)) + ->transformWith($this->getTransformer(LocationTransformer::class)) + ->toArray(); + } + + /** + * Store a new location on the Panel and return a HTTP/201 response code with the + * new location attached. + * + * @param \Pterodactyl\Http\Controllers\Api\Application\Locations\StoreLocationRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + 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, + ]), + ]) + ->respond(201); + } + + /** + * Update a location on the Panel and return the updated record to the user. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Locations\UpdateLocationRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateLocationRequest $request): array + { + $location = $this->updateService->handle($request->getModel(Location::class), $request->validated()); + + return $this->fractal->item($location) + ->transformWith($this->getTransformer(LocationTransformer::class)) + ->toArray(); + } + + /** + * Delete a location from the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Locations\DeleteLocationRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException + */ + public function delete(DeleteLocationRequest $request): Response + { + $this->deletionService->handle($request->getModel(Location::class)); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php new file mode 100644 index 000000000..21ce4ec9f --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nests/EggController.php @@ -0,0 +1,61 @@ +repository = $repository; + } + + /** + * Return all eggs that exist for a given nest. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest $request + * @return array + */ + public function index(GetEggsRequest $request): array + { + $eggs = $this->repository->findWhere([ + ['nest_id', '=', $request->getModel(Nest::class)->id], + ]); + + return $this->fractal->collection($eggs) + ->transformWith($this->getTransformer(EggTransformer::class)) + ->toArray(); + } + + /** + * Return a single egg that exists on the specified nest. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest $request + * @return array + */ + public function view(GetEggRequest $request): array + { + return $this->fractal->item($request->getModel(Egg::class)) + ->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 new file mode 100644 index 000000000..adeacc56c --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -0,0 +1,57 @@ +repository = $repository; + } + + /** + * Return all Nests that exist on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest $request + * @return array + */ + public function index(GetNestsRequest $request): array + { + $nests = $this->repository->paginated(50); + + return $this->fractal->collection($nests) + ->transformWith($this->getTransformer(NestTransformer::class)) + ->toArray(); + } + + /** + * Return information about a single Nest model. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest $request + * @return array + */ + public function view(GetNestsRequest $request): array + { + return $this->fractal->item($request->getModel(Nest::class)) + ->transformWith($this->getTransformer(NestTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php new file mode 100644 index 000000000..b63f3e203 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -0,0 +1,99 @@ +assignmentService = $assignmentService; + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * Return all of the allocations that exist for a given node. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest $request + * @return array + */ + public function index(GetAllocationsRequest $request): array + { + $allocations = $this->repository->getPaginatedAllocationsForNode( + $request->getModel(Node::class)->id, 50 + ); + + return $this->fractal->collection($allocations) + ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->toArray(); + } + + /** + * Store new allocations for a given node. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function store(StoreAllocationRequest $request): array + { + $this->assignmentService->handle($request->getModel(Node::class), $request->validated()); + + return response('', 204); + } + + /** + * Delete a specific allocation from the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\DeleteAllocationRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException + */ + public function delete(DeleteAllocationRequest $request): Response + { + $this->deletionService->handle($request->getModel(Allocation::class)); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php new file mode 100644 index 000000000..e9f679006 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -0,0 +1,151 @@ +repository = $repository; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->updateService = $updateService; + } + + /** + * Return all of the nodes currently available on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest $request + * @return array + */ + public function index(GetNodesRequest $request): array + { + $nodes = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + + return $this->fractal->collection($nodes) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } + + /** + * Return data for a single instance of a node. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest $request + * @return array + */ + public function view(GetNodeRequest $request): array + { + return $this->fractal->item($request->getModel(Node::class)) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } + + /** + * Create a new node on the Panel. Returns the created node and a HTTP/201 + * status response on success. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + 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, + ]), + ]) + ->respond(201); + } + + /** + * Update an existing node on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateNodeRequest $request): array + { + $node = $this->updateService->returnUpdatedModel()->handle( + $request->getModel(Node::class), $request->validated() + ); + + return $this->fractal->item($node) + ->transformWith($this->getTransformer(NodeTransformer::class)) + ->toArray(); + } + + /** + * Deletes a given node from the Panel as long as there are no servers + * currently attached to it. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function delete(DeleteNodeRequest $request): Response + { + $this->deletionService->handle($request->getModel(Node::class)); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php new file mode 100644 index 000000000..05512a4ee --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -0,0 +1,140 @@ +databaseManagementService = $databaseManagementService; + $this->databasePasswordService = $databasePasswordService; + $this->repository = $repository; + } + + /** + * Return a listing of all databases currently available to a single + * server. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request + * @return array + */ + public function index(GetServerDatabasesRequest $request): array + { + $databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id); + + return $this->fractal->collection($databases) + ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->toArray(); + } + + /** + * Return a single server database. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request + * @return array + */ + public function view(GetServerDatabaseRequest $request): array + { + return $this->fractal->item($request->getModel(Database::class)) + ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->toArray(); + } + + /** + * Reset the password for a specific server database. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function resetPassword(ServerDatabaseWriteRequest $request): Response + { + $this->databasePasswordService->handle($request->getModel(Database::class), str_random(24)); + + return response('', 204); + } + + /** + * Create a new database on the Panel for a given server. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreServerDatabaseRequest $request): JsonResponse + { + $server = $request->getModel(Server::class); + $database = $this->databaseManagementService->create($server->id, $request->validated()); + + return $this->fractal->item($database) + ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->addMeta([ + 'resource' => route('api.application.servers.databases.view', [ + 'server' => $server->id, + 'database' => $database->id, + ]), + ]) + ->respond(201); + } + + /** + * Handle a request to delete a specific server database from the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function delete(ServerDatabaseWriteRequest $request): Response + { + $this->databaseManagementService->delete($request->getModel(Database::class)->id); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php new file mode 100644 index 000000000..f869393af --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -0,0 +1,118 @@ +creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * Return all of the servers that currently exist on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest $request + * @return array + */ + public function index(GetServersRequest $request): array + { + $servers = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + + return $this->fractal->collection($servers) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } + + /** + * Create a new server on the system. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @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()); + + return $this->fractal->item($server) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->respond(201); + } + + /** + * Show a single server transformed for the application API. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @return array + */ + public function view(ServerWriteRequest $request): array + { + return $this->fractal->item($request->getModel(Server::class)) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } + + /** + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @param \Pterodactyl\Models\Server $server + * @param string $force + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response + { + $this->deletionService->withForce($force === 'force')->handle($server); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php new file mode 100644 index 000000000..e544c138a --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -0,0 +1,80 @@ +buildModificationService = $buildModificationService; + $this->detailsModificationService = $detailsModificationService; + } + + /** + * Update the details for a specific server. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerDetailsRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function details(UpdateServerDetailsRequest $request): array + { + $server = $this->detailsModificationService->returnUpdatedModel()->handle( + $request->getModel(Server::class), $request->validated() + ); + + return $this->fractal->item($server) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } + + /** + * Update the build details for a specific server. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function build(UpdateServerBuildConfigurationRequest $request): array + { + $server = $this->buildModificationService->handle($request->getModel(Server::class), $request->validated()); + + return $this->fractal->item($server) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php new file mode 100644 index 000000000..9ab324d7e --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -0,0 +1,114 @@ +rebuildService = $rebuildService; + $this->reinstallServerService = $reinstallServerService; + $this->suspensionService = $suspensionService; + } + + /** + * Suspend a server on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function suspend(ServerWriteRequest $request): Response + { + $this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_SUSPEND); + + return $this->returnNoContent(); + } + + /** + * Unsuspend a server on the Panel. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function unsuspend(ServerWriteRequest $request): Response + { + $this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_UNSUSPEND); + + return $this->returnNoContent(); + } + + /** + * Mark a server as needing to be reinstalled. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function reinstall(ServerWriteRequest $request): Response + { + $this->reinstallServerService->reinstall($request->getModel(Server::class)); + + return $this->returnNoContent(); + } + + /** + * Mark a server as needing its container rebuilt the next time it is started. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function rebuild(ServerWriteRequest $request): Response + { + $this->rebuildService->handle($request->getModel(Server::class)); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php new file mode 100644 index 000000000..e6b8015d8 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -0,0 +1,49 @@ +modificationService = $modificationService; + } + + /** + * Update the startup and environment settings for a specific server. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest $request + * @return array + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(UpdateServerStartupRequest $request): array + { + $server = $this->modificationService->handle($request->getModel(Server::class), $request->validated()); + + return $this->fractal->item($server) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php new file mode 100644 index 000000000..f58138173 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -0,0 +1,23 @@ +fractal->item($request->getUserModel()) + ->transformWith($this->getTransformer(UserTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php new file mode 100644 index 000000000..d845c9441 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -0,0 +1,178 @@ +creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->updateService = $updateService; + } + + /** + * Handle request to list all users on the panel. Returns a JSON-API representation + * of a collection of users including any defined relations passed in + * the request. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest $request + * @return array + */ + public function index(GetUsersRequest $request): array + { + $users = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + + return $this->fractal->collection($users) + ->transformWith($this->getTransformer(UserTransformer::class)) + ->toArray(); + } + + /** + * Handle a request to view a single user. Includes any relations that + * were defined in the request. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest $request + * @return array + */ + public function view(GetUsersRequest $request): array + { + return $this->fractal->item($request->getModel(User::class)) + ->transformWith($this->getTransformer(UserTransformer::class)) + ->toArray(); + } + + /** + * Update an existing user on the system and return the response. Returns the + * updated user model response on success. Supports handling of token revocation + * errors when switching a user from an admin to a normal user. + * + * Revocation errors are returned under the 'revocation_errors' key in the response + * meta. If there are no errors this is an empty array. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateUserRequest $request): array + { + $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); + $collection = $this->updateService->handle($request->getModel(User::class), $request->validated()); + + $errors = []; + if (! empty($collection->get('exceptions'))) { + foreach ($collection->get('exceptions') as $node => $exception) { + /** @var \GuzzleHttp\Exception\RequestException $exception */ + /** @var \GuzzleHttp\Psr7\Response|null $response */ + $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; + $message = trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ]); + + $errors[] = ['message' => $message, 'node' => $node]; + } + } + + $response = $this->fractal->item($collection->get('model')) + ->transformWith($this->getTransformer(UserTransformer::class)); + + if (count($errors) > 0) { + $response->addMeta([ + 'revocation_errors' => $errors, + ]); + } + + return $response->toArray(); + } + + /** + * Store a new user on the system. Returns the created user and a HTTP/201 + * header on successful creation. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + 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, + ]), + ]) + ->respond(201); + } + + /** + * Handle a request to delete a user from the Panel. Returns a HTTP/204 response + * on successful deletion. + * + * @param \Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest $request + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete(DeleteUserRequest $request): Response + { + $this->deletionService->handle($request->getModel(User::class)); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 9dd80824a..198da73f0 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -1,30 +1,9 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Auth; use Illuminate\Http\Request; +use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Password; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Events\Auth\FailedPasswordReset; @@ -32,42 +11,21 @@ use Illuminate\Foundation\Auth\SendsPasswordResetEmails; class ForgotPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset emails and - | includes a trait which assists in sending these notifications from - | your application to your users. Feel free to explore this trait. - | - */ - use SendsPasswordResetEmails; - /** - * Create a new controller instance. - * - * @return void - */ - public function __construct() - { - $this->middleware('guest'); - } - /** * Get the response for a failed password reset link. * * @param \Illuminate\Http\Request - * @param string $response + * @param string $response * @return \Illuminate\Http\RedirectResponse */ - protected function sendResetLinkFailedResponse(Request $request, $response) + protected function sendResetLinkFailedResponse(Request $request, $response): RedirectResponse { // As noted in #358 we will return success even if it failed // to avoid pointing out that an account does or does not // exist on the system. - event(new FailedPasswordReset($request->ip(), $request->only('email'))); + event(new FailedPasswordReset($request->ip(), $request->input('email'))); return $this->sendResetLinkResponse(Password::RESET_LINK_SENT); } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index e4ca0d2ca..127802d22 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -1,53 +1,57 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Auth; -use Auth; -use Cache; -use Crypt; use Illuminate\Http\Request; -use Pterodactyl\Models\User; +use Illuminate\Auth\AuthManager; use PragmaRX\Google2FA\Google2FA; +use Illuminate\Auth\Events\Failed; +use Illuminate\Http\RedirectResponse; use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Foundation\Auth\AuthenticatesUsers; +use Illuminate\Contracts\Cache\Repository as CacheRepository; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Illuminate\Contracts\Config\Repository as ConfigRepository; class LoginController extends Controller { - /* - |-------------------------------------------------------------------------- - | Login Controller - |-------------------------------------------------------------------------- - | - | This controller handles authenticating users for the application and - | redirecting them to your home screen. The controller uses a trait - | to conveniently provide its functionality to your applications. - | - */ use AuthenticatesUsers; + const USER_INPUT_FIELD = 'user'; + + /** + * @var \Illuminate\Auth\AuthManager + */ + private $auth; + + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + private $cache; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + private $repository; + + /** + * @var \PragmaRX\Google2FA\Google2FA + */ + private $google2FA; + /** * Where to redirect users after login / registration. * @@ -60,173 +64,184 @@ class LoginController extends Controller * * @var int */ - protected $lockoutTime = 120; + protected $lockoutTime; /** * After how many attempts should logins be throttled and locked. * * @var int */ - protected $maxLoginAttempts = 3; + protected $maxLoginAttempts; /** - * Create a new controller instance. + * LoginController constructor. * - * @return void + * @param \Illuminate\Auth\AuthManager $auth + * @param \Illuminate\Contracts\Cache\Repository $cache + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \PragmaRX\Google2FA\Google2FA $google2FA + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ - public function __construct() + public function __construct( + AuthManager $auth, + CacheRepository $cache, + ConfigRepository $config, + Encrypter $encrypter, + Google2FA $google2FA, + UserRepositoryInterface $repository + ) { + $this->auth = $auth; + $this->cache = $cache; + $this->config = $config; + $this->encrypter = $encrypter; + $this->google2FA = $google2FA; + $this->repository = $repository; + + $this->lockoutTime = $this->config->get('auth.lockout.time'); + $this->maxLoginAttempts = $this->config->get('auth.lockout.attempts'); + } + + /** + * Handle a login request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + * + * @throws \Illuminate\Validation\ValidationException + */ + public function login(Request $request) { - $this->middleware('guest', ['except' => 'logout']); + $username = $request->input(self::USER_INPUT_FIELD); + $useColumn = $this->getField($username); + + if ($this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + $this->sendLockoutResponse($request); + } + + try { + $user = $this->repository->findFirstWhere([[$useColumn, '=', $username]]); + } catch (RecordNotFoundException $exception) { + return $this->sendFailedLoginResponse($request); + } + + $validCredentials = password_verify($request->input('password'), $user->password); + if ($user->use_totp) { + $token = str_random(64); + $this->cache->put($token, ['user_id' => $user->id, 'valid_credentials' => $validCredentials], 5); + + return redirect()->route('auth.totp')->with('authentication_token', $token); + } + + if ($validCredentials) { + $this->auth->guard()->login($user, true); + + return $this->sendLoginResponse($request); + } + + return $this->sendFailedLoginResponse($request, $user); + } + + /** + * Handle a TOTP implementation page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function totp(Request $request) + { + $token = $request->session()->get('authentication_token'); + if (is_null($token) || $this->auth->guard()->user()) { + return redirect()->route('auth.login'); + } + + return view('auth.totp', ['verify_key' => $token]); + } + + /** + * Handle a login where the user is required to provide a TOTP authentication + * token. In order to add additional layers of security, users are not + * informed of an incorrect password until this stage, forcing them to + * provide a token on each login attempt. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + */ + public function loginUsingTotp(Request $request) + { + if (is_null($request->input('verify_token'))) { + return $this->sendFailedLoginResponse($request); + } + + try { + $cache = $this->cache->pull($request->input('verify_token'), []); + $user = $this->repository->find(array_get($cache, 'user_id', 0)); + } catch (RecordNotFoundException $exception) { + return $this->sendFailedLoginResponse($request); + } + + if (is_null($request->input('2fa_token')) || ! array_get($cache, 'valid_credentials')) { + return $this->sendFailedLoginResponse($request, $user); + } + + if (! $this->google2FA->verifyKey( + $this->encrypter->decrypt($user->totp_secret), + $request->input('2fa_token'), + $this->config->get('pterodactyl.auth.2fa.window') + )) { + return $this->sendFailedLoginResponse($request, $user); + } + + $this->auth->guard()->login($user, true); + + return $this->sendLoginResponse($request); } /** * Get the failed login response instance. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @return \Illuminate\Http\RedirectResponse */ - protected function sendFailedLoginResponse(Request $request) + protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null): RedirectResponse { $this->incrementLoginAttempts($request); + $this->fireFailedLoginEvent($user, [ + $this->getField($request->input(self::USER_INPUT_FIELD)) => $request->input(self::USER_INPUT_FIELD), + ]); - $errors = [$this->username() => trans('auth.failed')]; + $errors = [self::USER_INPUT_FIELD => trans('auth.failed')]; if ($request->expectsJson()) { return response()->json($errors, 422); } return redirect()->route('auth.login') - ->withInput($request->only($this->username(), 'remember')) + ->withInput($request->only(self::USER_INPUT_FIELD)) ->withErrors($errors); } /** - * Handle a login request to the application. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response|\Illuminate\Response\RedirectResponse - */ - public function login(Request $request) - { - // Check wether the user identifier is an email address or a username - $checkField = str_contains($request->input('user'), '@') ? 'email' : 'username'; - - if ($this->hasTooManyLoginAttempts($request)) { - $this->fireLockoutEvent($request); - - return $this->sendLockoutResponse($request); - } - - // Determine if the user even exists. - $user = User::where($checkField, $request->input($this->username()))->first(); - if (! $user) { - return $this->sendFailedLoginResponse($request); - } - - // If user uses 2FA, redirect to that page. - if ($user->use_totp) { - $token = str_random(64); - Cache::put($token, [ - 'user_id' => $user->id, - 'credentials' => Crypt::encrypt(serialize([ - $checkField => $request->input($this->username()), - 'password' => $request->input('password'), - ])), - ], 5); - - return redirect()->route('auth.totp') - ->with('authentication_token', $token) - ->with('remember', $request->has('remember')); - } - - $attempt = Auth::attempt([ - $checkField => $request->input($this->username()), - 'password' => $request->input('password'), - 'use_totp' => 0, - ], $request->has('remember')); - - if ($attempt) { - return $this->sendLoginResponse($request); - } - - // Login failed, send response. - return $this->sendFailedLoginResponse($request); - } - - /** - * Handle a TOTP implementation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - public function totp(Request $request) - { - $token = $request->session()->get('authentication_token'); - - if (is_null($token) || Auth::user()) { - return redirect()->route('auth.login'); - } - - return view('auth.totp', [ - 'verify_key' => $token, - 'remember' => $request->session()->get('remember'), - ]); - } - - /** - * Handle a TOTP input. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function totpCheckpoint(Request $request) - { - $G2FA = new Google2FA(); - - if (is_null($request->input('verify_token'))) { - return $this->sendFailedLoginResponse($request); - } - - $cache = Cache::pull($request->input('verify_token')); - $user = User::where('id', $cache['user_id'])->first(); - - if (! $user || ! $cache) { - $this->sendFailedLoginResponse($request); - } - - if (is_null($request->input('2fa_token'))) { - return $this->sendFailedLoginResponse($request); - } - - try { - $credentials = unserialize(Crypt::decrypt($cache['credentials'])); - } catch (\Illuminate\Contracts\Encryption\DecryptException $ex) { - return $this->sendFailedLoginResponse($request); - } - - if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) { - event(new \Illuminate\Auth\Events\Failed($user, $credentials)); - - return $this->sendFailedLoginResponse($request); - } - - $attempt = Auth::attempt($credentials, $request->has('remember')); - - if ($attempt) { - return $this->sendLoginResponse($request); - } - - // Login failed, send response. - return $this->sendFailedLoginResponse($request); - } - - /** - * Get the login username to be used by the controller. + * Determine if the user is logging in using an email or username,. * + * @param string $input * @return string */ - public function username() + private function getField(string $input = null): string { - return 'user'; + return str_contains($input, '@') ? 'email' : 'username'; + } + + /** + * Fire a failed login event. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param array $credentials + */ + private function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) + { + event(new Failed($user, $credentials)); } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php deleted file mode 100644 index d28692293..000000000 --- a/app/Http/Controllers/Auth/RegisterController.php +++ /dev/null @@ -1,71 +0,0 @@ -middleware('guest'); - } - - /** - * Get a validator for an incoming registration request. - * - * @param array $data - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - return Validator::make($data, [ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|min:6|confirmed', - ]); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return User - */ - protected function create(array $data) - { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); - } -} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 628f55f42..226958416 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -2,23 +2,11 @@ namespace Pterodactyl\Http\Controllers\Auth; -use Pterodactyl\Models\User; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; class ResetPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset requests - | and uses a simple trait to include this behavior. You're free to - | explore this trait and override any methods you wish to tweak. - | - */ - use ResetsPasswords; /** @@ -28,27 +16,17 @@ class ResetPasswordController extends Controller */ public $redirectTo = '/'; - /** - * Create a new controller instance. - * - * @return void - */ - public function __construct() - { - $this->middleware('guest'); - } - /** * Return the rules used when validating password reset. * * @return array */ - protected function rules() + protected function rules(): array { return [ 'token' => 'required', 'email' => 'required|email', - 'password' => 'required|confirmed|' . User::PASSWORD_RULES, + 'password' => 'required|confirmed|min:8', ]; } } diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/APIController.php deleted file mode 100644 index 9c3816db3..000000000 --- a/app/Http/Controllers/Base/APIController.php +++ /dev/null @@ -1,120 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Base; - -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\APIKey; -use Pterodactyl\Models\APIPermission; -use Pterodactyl\Repositories\APIRepository; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; - -class APIController extends Controller -{ - /** - * Display base API index page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - return view('base.api.index', [ - 'keys' => APIKey::where('user_id', $request->user()->id)->get(), - ]); - } - - /** - * Display API key creation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function create(Request $request) - { - return view('base.api.new', [ - 'permissions' => [ - 'user' => collect(APIPermission::permissions())->pull('_user'), - 'admin' => collect(APIPermission::permissions())->except('_user')->toArray(), - ], - ]); - } - - /** - * Handle saving new API key. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function store(Request $request) - { - try { - $repo = new APIRepository($request->user()); - $secret = $repo->create($request->intersect([ - 'memo', 'allowed_ips', - 'admin_permissions', 'permissions', - ])); - Alert::success('An API Key-Pair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); - - return redirect()->route('account.api'); - } catch (DisplayValidationException $ex) { - return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash(); - } - - return redirect()->route('account.api.new')->withInput(); - } - - /** - * Handle revoking API key. - * - * @param \Illuminate\Http\Request $request - * @param string $key - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response - */ - public function revoke(Request $request, $key) - { - try { - $repo = new APIRepository($request->user()); - $repo->revoke($key); - - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An error occured while attempting to remove this key.', - ], 503); - } - } -} diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php index 10c33e380..b6a433bb4 100644 --- a/app/Http/Controllers/Base/AccountController.php +++ b/app/Http/Controllers/Base/AccountController.php @@ -1,104 +1,71 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Base; -use Log; -use Alert; -use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Users\UserUpdateService; +use Pterodactyl\Http\Requests\Base\AccountDataFormRequest; class AccountController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Users\UserUpdateService + */ + protected $updateService; + + /** + * AccountController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + */ + public function __construct(AlertsMessageBag $alert, UserUpdateService $updateService) + { + $this->alert = $alert; + $this->updateService = $updateService; + } + /** * Display base account information page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('base.account'); } /** - * Update details for a users account. + * Update details for a user's account. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Base\AccountDataFormRequest $request * @return \Illuminate\Http\RedirectResponse - * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function update(Request $request) + public function update(AccountDataFormRequest $request) { $data = []; - - // Request to update account Password if ($request->input('do_action') === 'password') { - $this->validate($request, [ - 'current_password' => 'required', - 'new_password' => 'required|confirmed|' . User::PASSWORD_RULES, - 'new_password_confirmation' => 'required', - ]); - $data['password'] = $request->input('new_password'); - - // Request to update account Email } elseif ($request->input('do_action') === 'email') { $data['email'] = $request->input('new_email'); - - // Request to update account Identity } elseif ($request->input('do_action') === 'identity') { $data = $request->only(['name_first', 'name_last', 'username']); - - // Unknown, hit em with a 404 - } else { - return abort(404); } - if ( - in_array($request->input('do_action'), ['email', 'password']) - && ! password_verify($request->input('current_password'), $request->user()->password) - ) { - Alert::danger(trans('base.account.invalid_pass'))->flash(); - - return redirect()->route('account'); - } - - try { - $repo = new UserRepository; - $repo->update($request->user()->id, $data); - Alert::success('Your account details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('account')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger(trans('base.account.exception'))->flash(); - } + $this->updateService->setUserLevel(User::USER_LEVEL_USER); + $this->updateService->handle($request->user(), $data); + $this->alert->success(trans('base.account.details_updated'))->flash(); return redirect()->route('account'); } diff --git a/app/Http/Controllers/Base/AccountKeyController.php b/app/Http/Controllers/Base/AccountKeyController.php new file mode 100644 index 000000000..04563ca8a --- /dev/null +++ b/app/Http/Controllers/Base/AccountKeyController.php @@ -0,0 +1,107 @@ +alert = $alert; + $this->keyService = $keyService; + $this->repository = $repository; + } + + /** + * Display a listing of all account API keys. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request): View + { + return view('base.api.index', [ + 'keys' => $this->repository->getAccountKeys($request->user()), + ]); + } + + /** + * Display account API key creation page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function create(Request $request): View + { + return view('base.api.new'); + } + + /** + * Handle saving new account API key. + * + * @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreAccountKeyRequest $request) + { + $this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ + 'user_id' => $request->user()->id, + 'allowed_ips' => $request->input('allowed_ips'), + 'memo' => $request->input('memo'), + ]); + + $this->alert->success(trans('base.api.index.keypair_created'))->flash(); + + return redirect()->route('account.api'); + } + + /** + * Delete an account API key from the Panel via an AJAX request. + * + * @param \Illuminate\Http\Request $request + * @param string $identifier + * @return \Illuminate\Http\Response + */ + public function revoke(Request $request, string $identifier): Response + { + $this->repository->deleteAccountKey($request->user(), $identifier); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 556ea157c..70b5250f0 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -1,110 +1,90 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Base; use Illuminate\Http\Request; -use Pterodactyl\Models\Server; +use Pterodactyl\Models\User; +use GuzzleHttp\Exception\RequestException; use Pterodactyl\Http\Controllers\Controller; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class IndexController extends Controller { + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + protected $keyProviderService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * IndexController constructor. + * + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct( + DaemonKeyProviderService $keyProviderService, + DaemonServerRepositoryInterface $daemonRepository, + ServerRepositoryInterface $repository + ) { + $this->daemonRepository = $daemonRepository; + $this->keyProviderService = $keyProviderService; + $this->repository = $repository; + } + /** * Returns listing of user's servers. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function getIndex(Request $request) { - $servers = $request->user()->access()->with('user'); + $servers = $this->repository->setSearchTerm($request->input('query'))->filterUserAccessServers( + $request->user(), User::FILTER_LEVEL_ALL + ); - if (! is_null($request->input('query'))) { - $servers->search($request->input('query')); - } - - return view('base.index', [ - 'servers' => $servers->paginate(config('pterodactyl.paginate.frontend.servers')), - ]); - } - - /** - * Generate a random string. - * - * @param \Illuminate\Http\Request $request - * @param int $length - * @return string - * @deprecated - */ - public function getPassword(Request $request, $length = 16) - { - $length = ($length < 8) ? 8 : $length; - - $returnable = false; - while (! $returnable) { - $generated = str_random($length); - if (preg_match('/[A-Z]+[a-z]+[0-9]+/', $generated)) { - $returnable = true; - } - } - - return $generated; + return view('base.index', ['servers' => $servers]); } /** * Returns status of the server in a JSON response used for populating active status list. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse + * @throws \Exception */ public function status(Request $request, $uuid) { - $server = Server::byUuid($uuid); - - if (! $server) { - return response()->json([], 404); - } + $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]); + $token = $this->keyProviderService->handle($server, $request->user()); if (! $server->installed) { return response()->json(['status' => 20]); - } - - if ($server->suspended) { + } elseif ($server->suspended) { return response()->json(['status' => 30]); } try { - $res = $server->guzzleClient()->request('GET', '/server'); - if ($res->getStatusCode() === 200) { - return response()->json(json_decode($res->getBody())); - } - } catch (\Exception $e) { - // + $response = $this->daemonRepository->setServer($server)->setToken($token)->details(); + } catch (RequestException $exception) { + throw new HttpException(500, $exception->getMessage()); } - return response()->json([]); + return response()->json(json_decode($response->getBody())); } } diff --git a/app/Http/Controllers/Base/LanguageController.php b/app/Http/Controllers/Base/LanguageController.php deleted file mode 100644 index 67d04f66c..000000000 --- a/app/Http/Controllers/Base/LanguageController.php +++ /dev/null @@ -1,71 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Base; - -use Auth; -use Session; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use Pterodactyl\Http\Controllers\Controller; - -class LanguageController extends Controller -{ - /** - * A list of supported languages on the panel. - * - * @var array - */ - protected $languages = [ - 'de' => 'German', - 'en' => 'English', - 'et' => 'Estonian', - 'nb' => 'Norwegian', - 'nl' => 'Dutch', - 'pt' => 'Portuguese', - 'ro' => 'Romanian', - 'ru' => 'Russian', - ]; - - /** - * Sets the language for a user. - * - * @param \Illuminate\Http\Request $request - * @param string $language - * @return \Illuminate\Http\RedirectResponse - */ - public function setLanguage(Request $request, $language) - { - if (array_key_exists($language, $this->languages)) { - if (Auth::check()) { - $user = User::findOrFail(Auth::user()->id); - $user->language = $language; - $user->save(); - } - Session::put('applocale', $language); - } - - return redirect()->back(); - } -} diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php index 052a7a527..61c45d743 100644 --- a/app/Http/Controllers/Base/SecurityController.php +++ b/app/Http/Controllers/Base/SecurityController.php @@ -1,48 +1,80 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Base; -use Alert; -use Google2FA; use Illuminate\Http\Request; -use Pterodactyl\Models\Session; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Users\TwoFactorSetupService; +use Pterodactyl\Services\Users\ToggleTwoFactorService; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; +use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; class SecurityController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Users\ToggleTwoFactorService + */ + protected $toggleTwoFactorService; + + /** + * @var \Pterodactyl\Services\Users\TwoFactorSetupService + */ + protected $twoFactorSetupService; + + /** + * SecurityController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository + * @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService + * @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService + */ + public function __construct( + AlertsMessageBag $alert, + ConfigRepository $config, + SessionRepositoryInterface $repository, + ToggleTwoFactorService $toggleTwoFactorService, + TwoFactorSetupService $twoFactorSetupService + ) { + $this->alert = $alert; + $this->config = $config; + $this->repository = $repository; + $this->toggleTwoFactorService = $toggleTwoFactorService; + $this->twoFactorSetupService = $twoFactorSetupService; + } + /** * Returns Security Management Page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { + if ($this->config->get('session.driver') === 'database') { + $activeSessions = $this->repository->getUserSessions($request->user()->id); + } + return view('base.security', [ - 'sessions' => Session::where('user_id', $request->user()->id)->get(), + 'sessions' => $activeSessions ?? null, ]); } @@ -50,82 +82,69 @@ class SecurityController extends Controller * Generates TOTP Secret and returns popup data for user to verify * that they can generate a valid response. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function generateTotp(Request $request) { - $user = $request->user(); - - $user->totp_secret = Google2FA::generateSecretKey(); - $user->save(); - return response()->json([ - 'qrImage' => Google2FA::getQRCodeGoogleUrl( - 'Pterodactyl', - $user->email, - $user->totp_secret - ), - 'secret' => $user->totp_secret, + 'qrImage' => $this->twoFactorSetupService->handle($request->user()), ]); } /** * Verifies that 2FA token recieved is valid and will work on the account. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function setTotp(Request $request) { - if (! $request->has('token')) { - return response()->json([ - 'error' => 'Request is missing token parameter.', - ], 500); - } + try { + $this->toggleTwoFactorService->handle($request->user(), $request->input('token')); - $user = $request->user(); - if ($user->toggleTotp($request->input('token'))) { return response('true'); + } catch (TwoFactorAuthenticationTokenInvalid $exception) { + return response('false'); } - - return response('false'); } /** * Disables TOTP on an account. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function disableTotp(Request $request) { - if (! $request->has('token')) { - Alert::danger('Missing required `token` field in request.')->flash(); - - return redirect()->route('account.security'); + try { + $this->toggleTwoFactorService->handle($request->user(), $request->input('token'), false); + } catch (TwoFactorAuthenticationTokenInvalid $exception) { + $this->alert->danger(trans('base.security.2fa_disable_error'))->flash(); } - $user = $request->user(); - if ($user->toggleTotp($request->input('token'))) { - return redirect()->route('account.security'); - } - - Alert::danger('The TOTP token provided was invalid.')->flash(); - return redirect()->route('account.security'); } /** * Revokes a user session. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param string $id * @return \Illuminate\Http\RedirectResponse */ - public function revoke(Request $request, $id) + public function revoke(Request $request, string $id) { - Session::where('user_id', $request->user()->id)->findOrFail($id)->delete(); + $this->repository->deleteUserSession($request->user()->id, $id); return redirect()->route('account.security'); } diff --git a/app/Http/Controllers/Daemon/ActionController.php b/app/Http/Controllers/Daemon/ActionController.php index 0a054218c..1c67ec6cd 100644 --- a/app/Http/Controllers/Daemon/ActionController.php +++ b/app/Http/Controllers/Daemon/ActionController.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Daemon; @@ -35,7 +20,7 @@ class ActionController extends Controller /** * Handles download request from daemon. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function authenticateDownload(Request $request) @@ -57,7 +42,7 @@ class ActionController extends Controller /** * Handles install toggle request from daemon. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function markInstall(Request $request) @@ -87,8 +72,8 @@ class ActionController extends Controller /** * Handles configuration data request from daemon. * - * @param \Illuminate\Http\Request $request - * @param string $token + * @param \Illuminate\Http\Request $request + * @param string $token * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response */ public function configuration(Request $request, $token) diff --git a/app/Http/Controllers/Daemon/OptionController.php b/app/Http/Controllers/Daemon/OptionController.php deleted file mode 100644 index 9eb1806dc..000000000 --- a/app/Http/Controllers/Daemon/OptionController.php +++ /dev/null @@ -1,64 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Daemon; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Pterodactyl\Http\Controllers\Controller; - -class OptionController extends Controller -{ - public function details(Request $request, $server) - { - $server = Server::with('allocation', 'option', 'variables.variable')->where('uuid', $server)->firstOrFail(); - - $environment = $server->variables->map(function ($item) { - return sprintf('%s=%s', $item->variable->env_variable, $item->variable_value); - }); - - $mergeInto = [ - 'STARTUP=' . $server->startup, - 'SERVER_MEMORY=' . $server->memory, - 'SERVER_IP=' . $server->allocation->ip, - 'SERVER_PORT=' . $server->allocation->port, - ]; - - if ($environment->count() === 0) { - $environment = collect($mergeInto); - } - - return response()->json([ - 'scripts' => [ - 'install' => (! $server->option->copy_script_install) ? null : str_replace(["\r\n", "\n", "\r"], "\n", $server->option->copy_script_install), - 'privileged' => $server->option->script_is_privileged, - ], - 'config' => [ - 'container' => $server->option->copy_script_container, - 'entry' => $server->option->copy_script_entry, - ], - 'env' => $environment->toArray(), - ]); - } -} diff --git a/app/Http/Controllers/Daemon/PackController.php b/app/Http/Controllers/Daemon/PackController.php index 419ae7c9c..ce482013f 100644 --- a/app/Http/Controllers/Daemon/PackController.php +++ b/app/Http/Controllers/Daemon/PackController.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Controllers\Daemon; @@ -34,8 +19,8 @@ class PackController extends Controller /** * Pulls an install pack archive from the system. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse */ public function pull(Request $request, $uuid) @@ -56,8 +41,8 @@ class PackController extends Controller /** * Returns the hash information for a pack. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse */ public function hash(Request $request, $uuid) @@ -80,11 +65,9 @@ class PackController extends Controller /** * Pulls an update pack archive from the system. * - * @param \Illuminate\Http\Request $request - * @return void + * @param \Illuminate\Http\Request $request */ public function pullUpdate(Request $request) { - // } } diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php deleted file mode 100644 index d461786f0..000000000 --- a/app/Http/Controllers/Daemon/ServiceController.php +++ /dev/null @@ -1,100 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Daemon; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Http\Controllers\Controller; - -class ServiceController extends Controller -{ - /** - * Returns a listing of all services currently on the system, - * as well as the associated files and the file hashes for - * caching purposes. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - */ - public function listServices(Request $request) - { - $response = []; - foreach (Service::all() as $service) { - $response[$service->folder] = [ - 'main.json' => sha1($this->getConfiguration($service->id)->toJson()), - 'index.js' => sha1($service->index_file), - ]; - } - - return response()->json($response); - } - - /** - * Returns the contents of the requested file for the given service. - * - * @param \Illuminate\Http\Request $request - * @param string $folder - * @param string $file - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\FileResponse - */ - public function pull(Request $request, $folder, $file) - { - $service = Service::where('folder', $folder)->firstOrFail(); - - if ($file === 'index.js') { - return response($service->index_file)->header('Content-Type', 'text/plain'); - } elseif ($file === 'main.json') { - return response()->json($this->getConfiguration($service->id)); - } - - return abort(404); - } - - /** - * Returns a `main.json` file based on the configuration - * of each service option. - * - * @param int $id - * @return \Illuminate\Support\Collection - */ - protected function getConfiguration($id) - { - $options = ServiceOption::where('service_id', $id)->get(); - - return $options->mapWithKeys(function ($item) use ($options) { - return [ - $item->tag => array_filter([ - 'symlink' => $options->where('id', $item->config_from)->pluck('tag')->pop(), - 'startup' => json_decode($item->config_startup), - 'stop' => $item->config_stop, - 'configs' => json_decode($item->config_files), - 'log' => json_decode($item->config_logs), - 'query' => 'none', - ]), - ]; - }); - } -} diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php deleted file mode 100644 index c22b0c202..000000000 --- a/app/Http/Controllers/Server/AjaxController.php +++ /dev/null @@ -1,211 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Server; - -use Log; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Repositories; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; - -class AjaxController extends Controller -{ - /** - * @var array - */ - protected $files = []; - - /** - * @var array - */ - protected $folders = []; - - /** - * @var string - */ - protected $directory; - - /** - * Returns a listing of files in a given directory for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View|\Illuminate\Http\Response - */ - public function postDirectoryList(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); - $prevDir = [ - 'header' => ($this->directory !== '/') ? $this->directory : '', - ]; - if ($this->directory !== '/') { - $prevDir['first'] = true; - } - - // Determine if we should show back links in the file browser. - // This code is strange, and could probably be rewritten much better. - $goBack = explode('/', trim($this->directory, '/')); - if (! empty(array_filter($goBack)) && count($goBack) >= 2) { - $prevDir['show'] = true; - array_pop($goBack); - $prevDir['link'] = '/' . implode('/', $goBack); - $prevDir['link_show'] = implode('/', $goBack) . '/'; - } - - $controller = new Repositories\Daemon\FileRepository($uuid); - - try { - $directoryContents = $controller->returnDirectoryListing($this->directory); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to load the requested directory, please try again.', 500); - } - - return view('server.files.list', [ - 'server' => $server, - 'files' => $directoryContents->files, - 'folders' => $directoryContents->folders, - 'editableMime' => Repositories\HelperRepository::editableFiles(), - 'directory' => $prevDir, - ]); - } - - /** - * Handles a POST request to save a file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function postSaveFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('save-files', $server); - - $controller = new Repositories\Daemon\FileRepository($uuid); - - try { - $controller->saveFileContents($request->input('file'), $request->input('contents')); - - return response(null, 204); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to save this file, please try again.', 500); - } - } - - /** - * Sets the primary allocation for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @deprecated - */ - public function postSetPrimary(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid)->load('allocations'); - $this->authorize('set-connection', $server); - - if ((int) $request->input('allocation') === $server->allocation_id) { - return response()->json([ - 'error' => 'You are already using this as your default connection.', - ], 409); - } - - try { - $allocation = $server->allocations->where('id', $request->input('allocation'))->where('server_id', $server->id)->first(); - if (! $allocation) { - return response()->json([ - 'error' => 'No allocation matching your request was found in the system.', - ], 422); - } - - $repo = new Repositories\ServerRepository; - $repo->changeBuild($server->id, [ - 'default' => $allocation->ip . ':' . $allocation->port, - ]); - - return response('The default connection for this server has been updated. Please be aware that you will need to restart your server for this change to go into effect.'); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage(), true), - ], 422); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 503); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the default connection for this server.', - ], 503); - } - } - - /** - * Resets a database password for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @deprecated - */ - public function postResetDatabasePassword(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('reset-db-password', $server); - - $database = Models\Database::where('server_id', $server->id)->findOrFail($request->input('database')); - $repo = new Repositories\DatabaseRepository; - - try { - $password = str_random(20); - $repo->password($database->id, $password); - - return response($password); - } catch (DisplayException $ex) { - return response()->json(['error' => $ex->getMessage()], 503); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled error occured while attempting to modify this database\'s password.', - ], 503); - } - } -} diff --git a/app/Http/Controllers/Server/ConsoleController.php b/app/Http/Controllers/Server/ConsoleController.php new file mode 100644 index 000000000..0c1959251 --- /dev/null +++ b/app/Http/Controllers/Server/ConsoleController.php @@ -0,0 +1,72 @@ +config = $config; + } + + /** + * Render server index page with the console and power options. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + + $this->setRequest($request)->injectJavascript([ + 'server' => [ + 'cpu' => $server->cpu, + ], + 'meta' => [ + 'saveFile' => route('server.files.save', $server->uuidShort), + 'csrfToken' => csrf_token(), + ], + 'config' => [ + 'console_count' => $this->config->get('pterodactyl.console.count'), + 'console_freq' => $this->config->get('pterodactyl.console.frequency'), + ], + ]); + + return view('server.index'); + } + + /** + * Render a stand-alone console in the browser. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + */ + public function console(Request $request): View + { + $this->setRequest($request)->injectJavascript(['config' => [ + 'console_count' => $this->config->get('pterodactyl.console.count'), + 'console_freq' => $this->config->get('pterodactyl.console.frequency'), + ]]); + + return view('server.console'); + } +} diff --git a/app/Http/Controllers/Server/DatabaseController.php b/app/Http/Controllers/Server/DatabaseController.php new file mode 100644 index 000000000..06636c4c0 --- /dev/null +++ b/app/Http/Controllers/Server/DatabaseController.php @@ -0,0 +1,77 @@ +passwordService = $passwordService; + $this->repository = $repository; + } + + /** + * Render the database listing for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('view-databases', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.databases.index', [ + 'databases' => $this->repository->getDatabasesForServer($server->id), + ]); + } + + /** + * Handle a request to update the password for a specific database. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(Request $request): JsonResponse + { + $this->authorize('reset-db-password', $request->attributes->get('server')); + + $password = str_random(20); + $this->passwordService->handle($request->attributes->get('database'), $password); + + return response()->json(['password' => $password]); + } +} diff --git a/app/Http/Controllers/Server/Files/DownloadController.php b/app/Http/Controllers/Server/Files/DownloadController.php new file mode 100644 index 000000000..79155a63a --- /dev/null +++ b/app/Http/Controllers/Server/Files/DownloadController.php @@ -0,0 +1,55 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\Http\Request; +use Illuminate\Cache\Repository; +use Illuminate\Http\RedirectResponse; +use Pterodactyl\Http\Controllers\Controller; + +class DownloadController extends Controller +{ + /** + * @var \Illuminate\Cache\Repository + */ + protected $cache; + + /** + * DownloadController constructor. + * + * @param \Illuminate\Cache\Repository $cache + */ + public function __construct(Repository $cache) + { + $this->cache = $cache; + } + + /** + * Setup a unique download link for a user to download a file from. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param string $file + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request, string $uuid, string $file): RedirectResponse + { + $server = $request->attributes->get('server'); + $this->authorize('download-files', $server); + + $token = str_random(40); + $node = $server->getRelation('node'); + $this->cache->tags(['Server:Downloads'])->put($token, ['server' => $server->uuid, 'path' => $file], 5); + + return redirect(sprintf('%s://%s:%s/v1/server/file/download/%s', $node->scheme, $node->fqdn, $node->daemonListen, $token)); + } +} diff --git a/app/Http/Controllers/Server/Files/FileActionsController.php b/app/Http/Controllers/Server/Files/FileActionsController.php new file mode 100644 index 000000000..ca3c093aa --- /dev/null +++ b/app/Http/Controllers/Server/Files/FileActionsController.php @@ -0,0 +1,120 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\View\View; +use Illuminate\Http\Request; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; + +class FileActionsController extends Controller +{ + use JavascriptInjection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $repository; + + /** + * FileActionsController constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository + */ + public function __construct(FileRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Display server file index list. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('list-files', $server); + + $this->setRequest($request)->injectJavascript([ + 'meta' => [ + 'directoryList' => route('server.files.directory-list', $server->uuidShort), + 'csrftoken' => csrf_token(), + ], + 'permissions' => [ + 'moveFiles' => $request->user()->can('move-files', $server), + 'copyFiles' => $request->user()->can('copy-files', $server), + 'compressFiles' => $request->user()->can('compress-files', $server), + 'decompressFiles' => $request->user()->can('decompress-files', $server), + 'createFiles' => $request->user()->can('create-files', $server), + 'downloadFiles' => $request->user()->can('download-files', $server), + 'deleteFiles' => $request->user()->can('delete-files', $server), + ], + ]); + + return view('server.files.index'); + } + + /** + * Render page to manually create a file in the panel. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function create(Request $request): View + { + $this->authorize('create-files', $request->attributes->get('server')); + $this->setRequest($request)->injectJavascript(); + + return view('server.files.add', [ + 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', + ]); + } + + /** + * Display a form to allow for editing of a file. + * + * @param \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest $request + * @param string $uuid + * @param string $file + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function view(UpdateFileContentsFormRequest $request, string $uuid, string $file): View + { + $server = $request->attributes->get('server'); + + $dirname = pathinfo($file, PATHINFO_DIRNAME); + try { + $content = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getContent($file); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + + $this->setRequest($request)->injectJavascript(['stat' => $request->attributes->get('file_stats')]); + + return view('server.files.edit', [ + 'file' => $file, + 'stat' => $request->attributes->get('file_stats'), + 'contents' => $content, + 'directory' => (in_array($dirname, ['.', './', '/'])) ? '/' : trim($dirname, '/') . '/', + ]); + } +} diff --git a/app/Http/Controllers/Server/Files/RemoteRequestController.php b/app/Http/Controllers/Server/Files/RemoteRequestController.php new file mode 100644 index 000000000..ab58037d0 --- /dev/null +++ b/app/Http/Controllers/Server/Files/RemoteRequestController.php @@ -0,0 +1,105 @@ +config = $config; + $this->repository = $repository; + } + + /** + * Return a listing of a servers file directory. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function directory(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('list-files', $server); + + $requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/'); + $directory = [ + 'header' => $requestDirectory !== '/' ? $requestDirectory : '', + 'first' => $requestDirectory !== '/', + ]; + + $goBack = explode('/', trim($requestDirectory, '/')); + if (! empty(array_filter($goBack)) && count($goBack) >= 2) { + array_pop($goBack); + + $directory['show'] = true; + $directory['link'] = '/' . implode('/', $goBack); + $directory['link_show'] = implode('/', $goBack) . '/'; + } + + try { + $listing = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getDirectory($requestDirectory); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception, true); + } + + return view('server.files.list', [ + 'files' => $listing['files'], + 'folders' => $listing['folders'], + 'editableMime' => $this->config->get('pterodactyl.files.editable'), + 'directory' => $directory, + ]); + } + + /** + * Put the contents of a file onto the daemon. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function store(Request $request): Response + { + $server = $request->attributes->get('server'); + $this->authorize('save-files', $server); + + try { + $this->repository->setServer($server)->setToken($request->attributes->get('server_token')) + ->putContent($request->input('file'), $request->input('contents') ?? ''); + + return response('', 204); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php deleted file mode 100644 index ce6f59595..000000000 --- a/app/Http/Controllers/Server/ServerController.php +++ /dev/null @@ -1,376 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Server; - -use Log; -use Alert; -use Cache; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Repositories\Daemon\FileRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServerController extends Controller -{ - /** - * Renders server index page for specified server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getIndex(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - - $server->js([ - 'meta' => [ - 'saveFile' => route('server.files.save', $server->uuidShort), - 'csrfToken' => csrf_token(), - ], - 'config' => [ - 'console_count' => config('pterodactyl.console.count'), - 'console_freq' => config('pterodactyl.console.frequency'), - ], - ]); - - return view('server.index', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders server console as an individual item. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getConsole(Request $request, $uuid) - { - \Debugbar::disable(); - $server = Models\Server::byUuid($uuid); - - $server->js([ - 'config' => [ - 'console_count' => config('pterodactyl.console.count'), - 'console_freq' => config('pterodactyl.console.frequency'), - ], - ]); - - return view('server.console', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders file overview page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getFiles(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $server->js([ - 'meta' => [ - 'directoryList' => route('server.files.directory-list', $server->uuidShort), - 'csrftoken' => csrf_token(), - ], - 'permissions' => [ - 'moveFiles' => $request->user()->can('move-files', $server), - 'copyFiles' => $request->user()->can('copy-files', $server), - 'compressFiles' => $request->user()->can('compress-files', $server), - 'decompressFiles' => $request->user()->can('decompress-files', $server), - 'createFiles' => $request->user()->can('create-files', $server), - 'downloadFiles' => $request->user()->can('download-files', $server), - 'deleteFiles' => $request->user()->can('delete-files', $server), - ], - ]); - - return view('server.files.index', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders add file page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getAddFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('create-files', $server); - - $server->js(); - - return view('server.files.add', [ - 'server' => $server, - 'node' => $server->node, - 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', - ]); - } - - /** - * Renders edit file page for a given file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getEditFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-files', $server); - - $fileInfo = (object) pathinfo($file); - $controller = new FileRepository($uuid); - - try { - $fileContent = $controller->returnFileContents($file); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('server.files.index', $uuid); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to load the requested file for editing, please try again.')->flash(); - - return redirect()->route('server.files.index', $uuid); - } - - $server->js([ - 'stat' => $fileContent['stat'], - ]); - - return view('server.files.edit', [ - 'server' => $server, - 'node' => $server->node, - 'file' => $file, - 'stat' => $fileContent['stat'], - 'contents' => $fileContent['file']->content, - 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/', - ]); - } - - /** - * Handles downloading a file for the user. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getDownloadFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('download-files', $server); - - $token = str_random(40); - Cache::tags(['Server:Downloads'])->put($token, [ - 'server' => $server->uuid, - 'path' => $file, - ], 5); - - return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $token); - } - - /** - * Returns the allocation overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getAllocation(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-allocation', $server); - $server->js(); - - return view('server.settings.allocation', [ - 'server' => $server->load(['allocations' => function ($query) { - $query->orderBy('ip', 'asc'); - $query->orderBy('port', 'asc'); - }]), - 'node' => $server->node, - ]); - } - - /** - * Returns the startup overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getStartup(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-startup', $server); - - $server->load(['node', 'allocation', 'variables']); - $variables = Models\ServiceVariable::where('option_id', $server->option_id)->get(); - - $replacements = [ - '{{SERVER_MEMORY}}' => $server->memory, - '{{SERVER_IP}}' => $server->allocation->ip, - '{{SERVER_PORT}}' => $server->allocation->port, - ]; - - $processed = str_replace(array_keys($replacements), array_values($replacements), $server->startup); - - foreach ($variables as $var) { - if ($var->user_viewable) { - $serverVar = $server->variables->where('variable_id', $var->id)->first(); - $var->server_set_value = $serverVar->variable_value ?? $var->default_value; - } else { - $var->server_set_value = '[hidden]'; - } - - $processed = str_replace('{{' . $var->env_variable . '}}', $var->server_set_value, $processed); - } - - $server->js(); - - return view('server.settings.startup', [ - 'server' => $server, - 'node' => $server->node, - 'variables' => $variables->where('user_viewable', 1), - 'service' => $server->service, - 'processedStartup' => $processed, - ]); - } - - /** - * Returns the database overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getDatabases(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-databases', $server); - - $server->load('node', 'databases.host'); - $server->js(); - - return view('server.settings.databases', [ - 'server' => $server, - 'node' => $server->node, - 'databases' => $server->databases, - ]); - } - - /** - * Returns the SFTP overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getSFTP(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-sftp', $server); - $server->js(); - - return view('server.settings.sftp', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Handles changing the SFTP password for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettingsSFTP(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('reset-sftp', $server); - - try { - $repo = new ServerRepository; - $repo->updateSFTPPassword($server->id, $request->input('sftp_pass')); - Alert::success('Successfully updated this servers SFTP password.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.settings.sftp', $uuid)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to update this server\'s SFTP settings.')->flash(); - } - - return redirect()->route('server.settings.sftp', $uuid); - } - - /** - * Handles changing the startup settings for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettingsStartup(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-startup', $server); - - try { - $repo = new ServerRepository; - $repo->updateStartup($server->id, $request->except('_token')); - Alert::success('Server startup variables were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.settings.startup', $uuid)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); - } - - return redirect()->route('server.settings.startup', $uuid); - } -} diff --git a/app/Http/Controllers/Server/Settings/AllocationController.php b/app/Http/Controllers/Server/Settings/AllocationController.php new file mode 100644 index 000000000..21baf7c0d --- /dev/null +++ b/app/Http/Controllers/Server/Settings/AllocationController.php @@ -0,0 +1,96 @@ +defaultAllocationService = $defaultAllocationService; + $this->hashids = $hashids; + $this->repository = $repository; + } + + /** + * Render the allocation management overview page for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('view-allocations', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.settings.allocation', [ + 'allocations' => $this->repository->findWhere([['server_id', '=', $server->id]]), + ]); + } + + /** + * Update the default allocation for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(Request $request): JsonResponse + { + $server = $request->attributes->get('server'); + $this->authorize('edit-allocation', $server); + + $allocation = $this->hashids->decodeFirst($request->input('allocation'), 0); + + try { + $this->defaultAllocationService->handle($server->id, $allocation); + } catch (AllocationDoesNotBelongToServerException $exception) { + return response()->json(['error' => 'No matching allocation was located for this server.'], 404); + } + + return response()->json(); + } +} diff --git a/app/Http/Controllers/Server/Settings/SftpController.php b/app/Http/Controllers/Server/Settings/SftpController.php new file mode 100644 index 000000000..b128ba5c9 --- /dev/null +++ b/app/Http/Controllers/Server/Settings/SftpController.php @@ -0,0 +1,26 @@ +setRequest($request)->injectJavascript(); + + return view('server.settings.sftp'); + } +} diff --git a/app/Http/Controllers/Server/Settings/StartupController.php b/app/Http/Controllers/Server/Settings/StartupController.php new file mode 100644 index 000000000..8f17022b5 --- /dev/null +++ b/app/Http/Controllers/Server/Settings/StartupController.php @@ -0,0 +1,96 @@ +alert = $alert; + $this->commandViewService = $commandViewService; + $this->modificationService = $modificationService; + } + + /** + * Render the server startup page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('view-startup', $server); + $this->setRequest($request)->injectJavascript(); + + $data = $this->commandViewService->handle($server->id); + + return view('server.settings.startup', [ + 'variables' => $data->get('variables'), + 'server_values' => $data->get('server_values'), + 'startup' => $data->get('startup'), + ]); + } + + /** + * Handle request to update the startup variables for a server. Authorization + * is handled in the form request. + * + * @param \Pterodactyl\Http\Requests\Server\UpdateStartupParametersFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateStartupParametersFormRequest $request): RedirectResponse + { + $this->modificationService->setUserLevel(User::USER_LEVEL_USER); + $this->modificationService->handle($request->attributes->get('server'), $request->normalize()); + $this->alert->success(trans('server.config.startup.edited'))->flash(); + + return redirect()->route('server.settings.startup', ['server' => $request->attributes->get('server')->uuidShort]); + } +} diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index 9a8b35075..b507bbb45 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -1,86 +1,113 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Server; -use Log; -use Auth; -use Alert; -use Pterodactyl\Models; +use Illuminate\View\View; use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Http\Response; +use Pterodactyl\Models\Permission; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\SubuserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Subusers\SubuserUpdateService; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Services\Subusers\SubuserCreationService; +use Pterodactyl\Services\Subusers\SubuserDeletionService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest; class SubuserController extends Controller { + use JavascriptInjection; + + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserCreationService + */ + protected $subuserCreationService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserDeletionService + */ + protected $subuserDeletionService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserUpdateService + */ + protected $subuserUpdateService; + + /** + * SubuserController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Subusers\SubuserCreationService $subuserCreationService + * @param \Pterodactyl\Services\Subusers\SubuserDeletionService $subuserDeletionService + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + * @param \Pterodactyl\Services\Subusers\SubuserUpdateService $subuserUpdateService + */ + public function __construct( + AlertsMessageBag $alert, + SubuserCreationService $subuserCreationService, + SubuserDeletionService $subuserDeletionService, + SubuserRepositoryInterface $repository, + SubuserUpdateService $subuserUpdateService + ) { + $this->alert = $alert; + $this->repository = $repository; + $this->subuserCreationService = $subuserCreationService; + $this->subuserDeletionService = $subuserDeletionService; + $this->subuserUpdateService = $subuserUpdateService; + } + /** * Displays the subuser overview index. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index(Request $request, $uuid) + public function index(Request $request): View { - $server = Models\Server::byUuid($uuid)->load('subusers.user'); + $server = $request->attributes->get('server'); $this->authorize('list-subusers', $server); - - $server->js(); + $this->setRequest($request)->injectJavascript(); return view('server.users.index', [ - 'server' => $server, - 'node' => $server->node, - 'subusers' => $server->subusers, + 'subusers' => $this->repository->findWhere([['server_id', '=', $server->id]]), ]); } /** - * Displays the a single subuser overview. + * Displays a single subuser overview. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function view(Request $request, $uuid, $id) + public function view(Request $request): View { - $server = Models\Server::byUuid($uuid)->load('node'); + $server = $request->attributes->get('server'); $this->authorize('view-subuser', $server); - $subuser = Models\Subuser::with('permissions', 'user') - ->where('server_id', $server->id)->findOrFail($id); - - $server->js(); + $subuser = $this->repository->getWithPermissions($request->attributes->get('subuser')); + $this->setRequest($request)->injectJavascript(); return view('server.users.view', [ - 'server' => $server, - 'node' => $server->node, 'subuser' => $subuser, - 'permlist' => Models\Permission::listPermissions(), - 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) { + 'permlist' => Permission::getPermissions(), + 'permissions' => $subuser->getRelation('permissions')->mapWithKeys(function ($item) { return [$item->permission => true]; }), ]); @@ -89,133 +116,83 @@ class SubuserController extends Controller /** * Handles editing a subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest $request + * @param string $uuid + * @param string $hash * @return \Illuminate\Http\RedirectResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(Request $request, $uuid, $id) + public function update(SubuserUpdateFormRequest $request, string $uuid, string $hash): RedirectResponse { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-subuser', $server); + $this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions', [])); + $this->alert->success(trans('server.users.user_updated'))->flash(); - $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); - - try { - if ($subuser->user_id === Auth::user()->id) { - throw new DisplayException('You are not authorized to edit you own account.'); - } - - $repo = new SubuserRepository; - $repo->update($subuser->id, [ - 'permissions' => $request->input('permissions'), - 'server' => $server->id, - 'user' => $subuser->user_id, - ]); - - Alert::success('Subuser permissions have successfully been updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $id, - ])->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to update this subuser.')->flash(); - } - - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $id, - ]); + return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'subuser' => $hash]); } /** * Display new subuser creation page. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create(Request $request, $uuid) + public function create(Request $request): View { - $server = Models\Server::byUuid($uuid); + $server = $request->attributes->get('server'); $this->authorize('create-subuser', $server); - $server->js(); + $this->setRequest($request)->injectJavascript(); - return view('server.users.new', [ - 'server' => $server, - 'permissions' => Models\Permission::listPermissions(), - 'node' => $server->node, - ]); + return view('server.users.new', ['permissions' => Permission::getPermissions()]); } /** * Handles creating a new subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException */ - public function store(Request $request, $uuid) + public function store(SubuserStoreFormRequest $request): RedirectResponse { - $server = Models\Server::byUuid($uuid); - $this->authorize('create-subuser', $server); + $server = $request->attributes->get('server'); - try { - $repo = new SubuserRepository; - $subuser = $repo->create($server->id, $request->only([ - 'permissions', 'email', - ])); - Alert::success('Successfully created new subuser.')->flash(); + $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', [])); + $this->alert->success(trans('server.users.user_assigned'))->flash(); - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $subuser->id, - ]); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to add a new subuser.')->flash(); - } - - return redirect()->route('server.subusers.new', $uuid)->withInput(); + return redirect()->route('server.subusers.view', [ + 'uuid' => $server->uuid, + 'id' => $subuser->hashid, + ]); } /** * Handles deleting a subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(Request $request, $uuid, $id) + public function delete(Request $request): Response { - $server = Models\Server::byUuid($uuid); + $server = $request->attributes->get('server'); $this->authorize('delete-subuser', $server); - try { - $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); + $this->subuserDeletionService->handle($request->attributes->get('subuser')); - $repo = new SubuserRepository; - $repo->delete($subuser->id); - - return response('', 204); - } catch (DisplayException $ex) { - response()->json([ - 'error' => $ex->getMessage(), - ], 422); - } catch (\Exception $ex) { - Log::error($ex); - response()->json([ - 'error' => 'An unknown error occured while attempting to delete this subuser.', - ], 503); - } + return response('', 204); } } diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php deleted file mode 100644 index a3908943a..000000000 --- a/app/Http/Controllers/Server/TaskController.php +++ /dev/null @@ -1,181 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Controllers\Server; - -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\TaskRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class TaskController extends Controller -{ - /** - * Display task index page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function index(Request $request, $uuid) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('list-tasks', $server); - $server->js(); - - return view('server.tasks.index', [ - 'server' => $server, - 'node' => $server->node, - 'tasks' => $server->tasks, - 'actions' => [ - 'command' => trans('server.tasks.actions.command'), - 'power' => trans('server.tasks.actions.power'), - ], - ]); - } - - /** - * Display new task page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function create(Request $request, $uuid) - { - $server = Server::byUuid($uuid); - $this->authorize('create-task', $server); - $server->js(); - - return view('server.tasks.new', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Handle creation of new task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function store(Request $request, $uuid) - { - $server = Server::byUuid($uuid); - $this->authorize('create-task', $server); - - $repo = new TaskRepository; - try { - $repo->create($server->id, $request->user()->id, $request->except([ - '_token', - ])); - - return redirect()->route('server.tasks', $uuid); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.tasks.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to create this task.')->flash(); - } - - return redirect()->route('server.tasks.new', $uuid); - } - - /** - * Handle deletion of a task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $uuid, $id) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('delete-task', $server); - - $task = $server->tasks->where('id', $id)->first(); - if (! $task) { - return response()->json([ - 'error' => 'No task by that ID was found associated with this server.', - ], 404); - } - - $repo = new TaskRepository; - try { - $repo->delete($id); - - return response()->json([], 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'A server error occured while attempting to delete this task.', - ], 503); - } - } - - /** - * Toggle the status of a task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function toggle(Request $request, $uuid, $id) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('toggle-task', $server); - - $task = $server->tasks->where('id', $id)->first(); - if (! $task) { - return response()->json([ - 'error' => 'No task by that ID was found associated with this server.', - ], 404); - } - - $repo = new TaskRepository; - try { - $resp = $repo->toggle($id); - - return response()->json([ - 'status' => $resp, - ]); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'A server error occured while attempting to toggle this task.', - ], 503); - } - } -} diff --git a/app/Http/Controllers/Server/Tasks/ActionController.php b/app/Http/Controllers/Server/Tasks/ActionController.php new file mode 100644 index 000000000..410d7c189 --- /dev/null +++ b/app/Http/Controllers/Server/Tasks/ActionController.php @@ -0,0 +1,70 @@ +processScheduleService = $processScheduleService; + $this->repository = $repository; + } + + /** + * Toggle a task to be active or inactive for a given server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function toggle(Request $request): Response + { + $server = $request->attributes->get('server'); + $schedule = $request->attributes->get('schedule'); + $this->authorize('toggle-schedule', $server); + + $this->repository->update($schedule->id, [ + 'is_active' => ! $schedule->is_active, + ]); + + return response('', 204); + } + + /** + * Trigger a schedule to run now. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function trigger(Request $request): Response + { + $server = $request->attributes->get('server'); + $this->authorize('toggle-schedule', $server); + + $this->processScheduleService->setRunTimeOverride(Carbon::now())->handle( + $request->attributes->get('schedule') + ); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php new file mode 100644 index 000000000..9805b5cff --- /dev/null +++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php @@ -0,0 +1,198 @@ +alert = $alert; + $this->creationService = $creationService; + $this->hashids = $hashids; + $this->repository = $repository; + $this->updateService = $updateService; + } + + /** + * Display the task page listing. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('list-schedules', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.schedules.index', [ + 'schedules' => $this->repository->findServerSchedules($server->id), + 'actions' => [ + 'command' => trans('server.schedule.actions.command'), + 'power' => trans('server.schedule.actions.power'), + ], + ]); + } + + /** + * Display the task creation page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function create(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('create-schedule', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.schedules.new'); + } + + /** + * Handle request to store a new schedule and tasks in the database. + * + * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException + */ + public function store(ScheduleCreationFormRequest $request): RedirectResponse + { + $server = $request->attributes->get('server'); + + $schedule = $this->creationService->handle($server, $request->normalize(), $request->getTasks()); + $this->alert->success(trans('server.schedule.schedule_created'))->flash(); + + return redirect()->route('server.schedules.view', [ + 'server' => $server->uuidShort, + 'schedule' => $schedule->hashid, + ]); + } + + /** + * Return a view to modify a schedule. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function view(Request $request): View + { + $server = $request->attributes->get('server'); + $schedule = $request->attributes->get('schedule'); + $this->authorize('view-schedule', $server); + + $this->setRequest($request)->injectJavascript([ + 'tasks' => $schedule->getRelation('tasks')->map(function ($task) { + /* @var \Pterodactyl\Models\Task $task */ + return collect($task->toArray())->only('action', 'time_offset', 'payload')->all(); + }), + ]); + + return view('server.schedules.view', ['schedule' => $schedule]); + } + + /** + * Update a specific parent task on the system. + * + * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException + */ + public function update(ScheduleCreationFormRequest $request): RedirectResponse + { + $server = $request->attributes->get('server'); + $schedule = $request->attributes->get('schedule'); + + $this->updateService->handle($schedule, $request->normalize(), $request->getTasks()); + $this->alert->success(trans('server.schedule.schedule_updated'))->flash(); + + return redirect()->route('server.schedules.view', [ + 'server' => $server->uuidShort, + 'schedule' => $schedule->hashid, + ]); + } + + /** + * Delete a parent task from the Panel. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function delete(Request $request): Response + { + $server = $request->attributes->get('server'); + $schedule = $request->attributes->get('schedule'); + $this->authorize('delete-schedule', $server); + + $this->repository->delete($schedule->id); + + return response('', 204); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a70895a3e..1d33e210d 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,39 @@ namespace Pterodactyl\Http; +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\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; +use Illuminate\Routing\Middleware\SubstituteBindings; +use Pterodactyl\Http\Middleware\AccessingValidServer; +use Illuminate\View\Middleware\ShareErrorsFromSession; +use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; +use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; +use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; +use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; +use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; +use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; +use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; +use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; +use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; +use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; +use Pterodactyl\Http\Middleware\Api\Application\AuthenticateKey; +use Pterodactyl\Http\Middleware\Api\Application\AuthenticateUser; +use Pterodactyl\Http\Middleware\Api\Application\SetSessionDriver; +use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; +use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Pterodactyl\Http\Middleware\Api\Application\AuthenticateIPAccess; +use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate; class Kernel extends HttpKernel { @@ -12,15 +44,11 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, - \Pterodactyl\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Pterodactyl\Http\Middleware\TrimStrings::class, - - /* - * Custom middleware applied to all routes. - */ - \Fideloper\Proxy\TrustProxies::class, + CheckForMaintenanceMode::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + TrustProxies::class, ]; /** @@ -30,18 +58,26 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ - \Pterodactyl\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, - \Pterodactyl\Http\Middleware\LanguageMiddleware::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + LanguageMiddleware::class, + RequireTwoFactorAuthentication::class, ], 'api' => [ - \Pterodactyl\Http\Middleware\HMACAuthorization::class, - 'throttle:60,1', - 'bindings', + 'throttle:120,1', + ApiSubstituteBindings::class, + SetSessionDriver::class, + AuthenticateKey::class, + AuthenticateUser::class, + AuthenticateIPAccess::class, + ], + 'daemon' => [ + SubstituteBindings::class, + DaemonAuthenticate::class, ], ]; @@ -51,16 +87,25 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ - 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, - 'server' => \Pterodactyl\Http\Middleware\CheckServer::class, - 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, - 'daemon' => \Pterodactyl\Http\Middleware\DaemonAuthenticate::class, - 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, - 'recaptcha' => \Pterodactyl\Http\Middleware\VerifyReCaptcha::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'guest' => RedirectIfAuthenticated::class, + 'server' => AccessingValidServer::class, + 'subuser.auth' => AuthenticateAsSubuser::class, + 'admin' => AdminAuthenticate::class, + 'daemon-old' => OldDaemonAuthenticate::class, + 'csrf' => VerifyCsrfToken::class, + 'throttle' => ThrottleRequests::class, + 'can' => Authorize::class, + 'bindings' => SubstituteBindings::class, + 'recaptcha' => VerifyReCaptcha::class, + + // Server specific middleware (used for authenticating access to resources) + // + // These are only used for individual server authentication, and not gloabl + // actions from other resources. They are defined in the route files. + 'server..database' => DatabaseBelongsToServer::class, + 'server..subuser' => SubuserBelongsToServer::class, + 'server..schedule' => ScheduleBelongsToServer::class, ]; } diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index 175210929..6307669c3 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -3,69 +3,31 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Middleware; use Closure; -use Illuminate\Contracts\Auth\Guard; +use Illuminate\Http\Request; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AdminAuthenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - - /** - * Create a new filter instance. - * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void - */ - public function __construct(Guard $auth) - { - $this->auth = $auth; - } - /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - if ($this->auth->guest()) { - if ($request->ajax()) { - return response('Unauthorized.', 401); - } else { - return redirect()->guest('auth/login'); - } - } - - if ($this->auth->user()->root_admin !== 1) { - return abort(403); + if (! $request->user() || ! $request->user()->root_admin) { + throw new AccessDeniedHttpException; } return $next($request); diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php new file mode 100644 index 000000000..b270be4ca --- /dev/null +++ b/app/Http/Middleware/Api/ApiSubstituteBindings.php @@ -0,0 +1,76 @@ + Allocation::class, + 'database' => Database::class, + 'egg' => Egg::class, + 'location' => Location::class, + 'nest' => Nest::class, + 'node' => Node::class, + 'server' => Server::class, + 'user' => User::class, + ]; + + /** + * Perform substitution of route parameters without triggering + * a 404 error if a model is not found. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $route = $request->route(); + + foreach (self::$mappings as $key => $model) { + $this->router->model($key, $model); + } + + $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/Application/AuthenticateIPAccess.php b/app/Http/Middleware/Api/Application/AuthenticateIPAccess.php new file mode 100644 index 000000000..6988c637d --- /dev/null +++ b/app/Http/Middleware/Api/Application/AuthenticateIPAccess.php @@ -0,0 +1,40 @@ +attributes->get('api_key'); + + if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { + return $next($request); + } + + $find = new IP($request->ip()); + foreach ($model->allowed_ips as $ip) { + if (Range::parse($ip)->contains($find)) { + return $next($request); + } + } + + throw new AccessDeniedHttpException('This IP address does not have permission to access the API using these credentials.'); + } +} diff --git a/app/Http/Middleware/Api/Application/AuthenticateKey.php b/app/Http/Middleware/Api/Application/AuthenticateKey.php new file mode 100644 index 000000000..30e6236ed --- /dev/null +++ b/app/Http/Middleware/Api/Application/AuthenticateKey.php @@ -0,0 +1,87 @@ +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. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Request $request, Closure $next) + { + if (is_null($request->bearerToken())) { + throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); + } + + $raw = $request->bearerToken(); + $identifier = substr($raw, 0, ApiKey::IDENTIFIER_LENGTH); + $token = substr($raw, ApiKey::IDENTIFIER_LENGTH); + + try { + $model = $this->repository->findFirstWhere([ + ['identifier', '=', $identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ]); + } catch (RecordNotFoundException $exception) { + throw new AccessDeniedHttpException; + } + + if (! hash_equals($this->encrypter->decrypt($model->token), $token)) { + throw new AccessDeniedHttpException; + } + + $this->auth->guard()->loginUsingId($model->user_id); + $request->attributes->set('api_key', $model); + $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Application/AuthenticateUser.php b/app/Http/Middleware/Api/Application/AuthenticateUser.php new file mode 100644 index 000000000..5bbce8296 --- /dev/null +++ b/app/Http/Middleware/Api/Application/AuthenticateUser.php @@ -0,0 +1,27 @@ +user()) || ! $request->user()->root_admin) { + throw new AccessDeniedHttpException; + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Application/SetSessionDriver.php b/app/Http/Middleware/Api/Application/SetSessionDriver.php new file mode 100644 index 000000000..c4660ec9b --- /dev/null +++ b/app/Http/Middleware/Api/Application/SetSessionDriver.php @@ -0,0 +1,52 @@ +app = $app; + $this->config = $config; + } + + /** + * Set the session for API calls to only last for the one request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + if ($this->app->environment() !== 'production') { + $this->app->make(LaravelDebugbar::class)->disable(); + } + + $this->config->set('session.driver', 'array'); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php new file mode 100644 index 000000000..c951b1b4a --- /dev/null +++ b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php @@ -0,0 +1,69 @@ +repository = $repository; + } + + /** + * Check if a request from the daemon can be properly attributed back to a single node instance. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\HttpException + */ + public function handle(Request $request, Closure $next) + { + if (in_array($request->route()->getName(), $this->except)) { + return $next($request); + } + + $token = $request->bearerToken(); + + if (is_null($token)) { + throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); + } + + try { + $node = $this->repository->findFirstWhere([['daemonSecret', '=', $token]]); + } catch (RecordNotFoundException $exception) { + throw new AccessDeniedHttpException; + } + + $request->attributes->set('node', $node); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index b6db005ef..d85cf95bf 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -3,43 +3,24 @@ namespace Pterodactyl\Http\Middleware; use Closure; -use Illuminate\Contracts\Auth\Guard; +use Illuminate\Http\Request; +use Illuminate\Auth\AuthenticationException; class Authenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - - /** - * Create a new filter instance. - * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void - */ - public function __construct(Guard $auth) - { - $this->auth = $auth; - } - /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - if ($this->auth->guest()) { - if ($request->ajax()) { - return response('Unauthorized.', 401); - } else { - return redirect()->guest('auth/login'); - } + if (! $request->user()) { + throw new AuthenticationException; } return $next($request); diff --git a/app/Http/Middleware/CheckServer.php b/app/Http/Middleware/CheckServer.php deleted file mode 100644 index 4cfe08191..000000000 --- a/app/Http/Middleware/CheckServer.php +++ /dev/null @@ -1,129 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Middleware; - -use Auth; -use Closure; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Illuminate\Auth\AuthenticationException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -class CheckServer -{ - /** - * The elquent model for the server. - * - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * The request object. - * - * @var \Illuminate\Http\Request - */ - protected $request; - - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - if (! Auth::user()) { - throw new AuthenticationException(); - } - - $this->request = $request; - $this->server = Server::byUuid($request->route()->server); - - if (! $this->exists()) { - return response()->view('errors.404', [], 404); - } - - if ($this->suspended()) { - return response()->view('errors.suspended', [], 403); - } - - if (! $this->installed()) { - return response()->view('errors.installing', [], 403); - } - - return $next($request); - } - - /** - * Determine if the server was found on the system. - * - * @return bool - */ - protected function exists() - { - if (! $this->server) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new NotFoundHttpException('The requested server was not found on the system.'); - } - } - - return (! $this->server) ? false : true; - } - - /** - * Determine if the server is suspended. - * - * @return bool - */ - protected function suspended() - { - if ($this->server->suspended) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new AccessDeniedHttpException('Server is suspended.'); - } - } - - return $this->server->suspended; - } - - /** - * Determine if the server is installed. - * - * @return bool - */ - protected function installed() - { - if ($this->server->installed !== 1) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new AccessDeniedHttpException('Server is completing install process.'); - } - } - - return $this->server->installed === 1; - } -} diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php index b924b6fb5..b8e83bf8c 100644 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -3,81 +3,66 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Middleware; use Closure; -use Pterodactyl\Models\Node; -use Illuminate\Contracts\Auth\Guard; +use Illuminate\Http\Request; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class DaemonAuthenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - /** * An array of route names to not apply this middleware to. * * @var array */ - protected $except = [ + private $except = [ 'daemon.configuration', ]; + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + private $repository; + /** * Create a new filter instance. * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @deprecated */ - public function __construct(Guard $auth) + public function __construct(NodeRepositoryInterface $repository) { - $this->auth = $auth; + $this->repository = $repository; } /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { if (in_array($request->route()->getName(), $this->except)) { return $next($request); } if (! $request->header('X-Access-Node')) { - return abort(403); + throw new AccessDeniedHttpException; } - $node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first(); - if (! $node) { - return abort(401); - } + $node = $this->repository->findFirstWhere(['daemonSecret' => $request->header('X-Access-Node')]); + $request->attributes->set('node', $node); return $next($request); } diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 8e8559f23..9c0cadd86 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -11,7 +11,5 @@ class EncryptCookies extends BaseEncrypter * * @var array */ - protected $except = [ - // - ]; + protected $except = []; } diff --git a/app/Http/Middleware/HMACAuthorization.php b/app/Http/Middleware/HMACAuthorization.php deleted file mode 100644 index 4f5c86f2b..000000000 --- a/app/Http/Middleware/HMACAuthorization.php +++ /dev/null @@ -1,229 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Http\Middleware; - -use Auth; -use Crypt; -use Config; -use Closure; -use Debugbar; -use IPTools\IP; -use IPTools\Range; -use Illuminate\Http\Request; -use Pterodactyl\Models\APIKey; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400 -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403 - -class HMACAuthorization -{ - /** - * The algorithm to use for handling HMAC encryption. - * - * @var string - */ - const HMAC_ALGORITHM = 'sha256'; - - /** - * Stored values from the Authorization header. - * - * @var array - */ - protected $token = []; - - /** - * The eloquent model for the API key. - * - * @var \Pterodactyl\Models\APIKey - */ - protected $key; - - /** - * The illuminate request object. - * - * @var \Illuminate\Http\Request - */ - private $request; - - /** - * Construct class instance. - * - * @return void - */ - public function __construct() - { - Debugbar::disable(); - Config::set('session.driver', 'array'); - } - - /** - * Handle an incoming request for the API. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - $this->request = $request; - - $this->checkBearer(); - $this->validateRequest(); - $this->validateIPAccess(); - $this->validateContents(); - - Auth::loginUsingId($this->key()->user_id); - - return $next($request); - } - - /** - * Checks that the Bearer token is provided and in a valid format. - * - * @return void - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - protected function checkBearer() - { - if (empty($this->request()->bearerToken())) { - throw new BadRequestHttpException('Request was missing required Authorization header.'); - } - - $token = explode('.', $this->request()->bearerToken()); - if (count($token) !== 2) { - throw new BadRequestHttpException('The Authorization header passed was not in a validate public/private key format.'); - } - - $this->token = [ - 'public' => $token[0], - 'hash' => $token[1], - ]; - } - - /** - * Determine if the request contains a valid public API key - * as well as permissions for the resource. - * - * @return void - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - protected function validateRequest() - { - $this->key = APIKey::where('public', $this->public())->first(); - if (! $this->key) { - throw new AccessDeniedHttpException('Unable to identify requester. Authorization token is invalid.'); - } - } - - /** - * Determine if the requesting IP address is allowed to use this API key. - * - * @return bool - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - protected function validateIPAccess() - { - if (! is_null($this->key()->allowed_ips)) { - foreach (json_decode($this->key()->allowed_ips) as $ip) { - if (Range::parse($ip)->contains(new IP($this->request()->ip()))) { - return true; - } - } - - throw new AccessDeniedHttpException('This IP address does not have permission to access the API using these credentials.'); - } - - return true; - } - - /** - * Determine if the HMAC sent in the request matches the one generated - * on the panel side. - * - * @return void - * - * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException - */ - protected function validateContents() - { - if (! hash_equals(base64_decode($this->hash()), $this->generateSignature())) { - throw new BadRequestHttpException('The HMAC for the request was invalid.'); - } - } - - /** - * Generate a HMAC from the request and known API secret key. - * - * @return string - */ - protected function generateSignature() - { - $content = urldecode($this->request()->fullUrl()) . $this->request()->getContent(); - - return hash_hmac(self::HMAC_ALGORITHM, $content, Crypt::decrypt($this->key()->secret), true); - } - - /** - * Return the public key passed in the Authorization header. - * - * @return string - */ - protected function public() - { - return $this->token['public']; - } - - /** - * Return the base64'd HMAC sent in the Authorization header. - * - * @return string - */ - protected function hash() - { - return $this->token['hash']; - } - - /** - * Return the API Key model. - * - * @return \Pterodactyl\Models\APIKey - */ - protected function key() - { - return $this->key; - } - - /** - * Return the Illuminate Request object. - * - * @return \Illuminate\Http\Request - */ - private function request() - { - return $this->request; - } -} diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 44553ebef..2e581f58f 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -3,53 +3,51 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Http\Middleware; -use Auth; use Closure; -use Session; -use Settings; -use Illuminate\Support\Facades\App; +use Illuminate\Http\Request; +use Illuminate\Foundation\Application; +use Illuminate\Contracts\Config\Repository; class LanguageMiddleware { + /** + * @var \Illuminate\Foundation\Application + */ + private $app; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * LanguageMiddleware constructor. + * + * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(Application $app, Repository $config) + { + $this->app = $app; + $this->config = $config; + } + /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - // if (Session::has('applocale')) { - // App::setLocale(Session::get('applocale')); - // } elseif (Auth::check() && isset(Auth::user()->language)) { - // Session::put('applocale', Auth::user()->language); - // App::setLocale(Auth::user()->language); - // } else { - // App::setLocale(Settings::get('default_language', 'en')); - // } - App::setLocale('en'); + $this->app->setLocale($this->config->get('app.locale', 'en')); return $next($request); } diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 731a1767f..8a5220cb5 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -3,22 +3,38 @@ namespace Pterodactyl\Http\Middleware; use Closure; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\Request; +use Illuminate\Auth\AuthManager; class RedirectIfAuthenticated { + /** + * @var \Illuminate\Auth\AuthManager + */ + private $authManager; + + /** + * RedirectIfAuthenticated constructor. + * + * @param \Illuminate\Auth\AuthManager $authManager + */ + public function __construct(AuthManager $authManager) + { + $this->authManager = $authManager; + } + /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $guard + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param string|null $guard * @return mixed */ - public function handle($request, Closure $next, $guard = null) + public function handle(Request $request, Closure $next, string $guard = null) { - if (Auth::guard($guard)->check()) { - return redirect(route('index')); + if ($this->authManager->guard($guard)->check()) { + return redirect()->route('index'); } return $next($request); diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php new file mode 100644 index 000000000..266983471 --- /dev/null +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -0,0 +1,96 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Middleware; + +use Closure; +use Illuminate\Http\Request; +use Prologue\Alerts\AlertsMessageBag; + +class RequireTwoFactorAuthentication +{ + const LEVEL_NONE = 0; + const LEVEL_ADMIN = 1; + const LEVEL_ALL = 2; + + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + private $alert; + + /** + * The names of routes that should be accessable without 2FA enabled. + * + * @var array + */ + protected $except = [ + 'account.security', + 'account.security.revoke', + 'account.security.totp', + 'account.security.totp.set', + 'account.security.totp.disable', + 'auth.totp', + 'auth.logout', + ]; + + /** + * The route to redirect a user to to enable 2FA. + * + * @var string + */ + protected $redirectRoute = 'account.security'; + + /** + * RequireTwoFactorAuthentication constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + */ + public function __construct(AlertsMessageBag $alert) + { + $this->alert = $alert; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + if (! $request->user()) { + return $next($request); + } + + if (in_array($request->route()->getName(), $this->except)) { + return $next($request); + } + + switch ((int) config('pterodactyl.auth.2fa_required')) { + case self::LEVEL_ADMIN: + if (! $request->user()->root_admin || $request->user()->use_totp) { + return $next($request); + } + break; + case self::LEVEL_ALL: + if ($request->user()->use_totp) { + return $next($request); + } + break; + case self::LEVEL_NONE: + default: + return $next($request); + } + + $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash(); + + return redirect()->route($this->redirectRoute); + } +} diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php new file mode 100644 index 000000000..ddca28251 --- /dev/null +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -0,0 +1,103 @@ +config = $config; + $this->repository = $repository; + $this->response = $response; + $this->session = $session; + } + + /** + * Determine if a given user has permission to access a server. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return \Illuminate\Http\Response|mixed + * + * @throws \Illuminate\Auth\AuthenticationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function handle(Request $request, Closure $next) + { + $attributes = $request->route()->parameter('server'); + $isApiRequest = $request->expectsJson() || $request->is(...$this->config->get('pterodactyl.json_routes', [])); + $server = $this->repository->getByUuid($attributes instanceof Server ? $attributes->uuid : $attributes); + + if ($server->suspended) { + if ($isApiRequest) { + throw new AccessDeniedHttpException('Server is suspended and cannot be accessed.'); + } + + return $this->response->view('errors.suspended', [], 403); + } + + // Servers can have install statuses other than 1 or 0, so don't check + // for a bool-type operator here. + if ($server->installed !== 1) { + if ($isApiRequest) { + throw new ConflictHttpException('Server is still completing the installation process.'); + } + + return $this->response->view('errors.installing', [], 409); + } + + // Store the server in the session. + // @todo remove from session. use request attributes. + $this->session->now('server_data.model', $server); + + // Add server to the request attributes. This will replace sessions + // as files are updated. + $request->attributes->set('server', $server); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Server/AuthenticateAsSubuser.php b/app/Http/Middleware/Server/AuthenticateAsSubuser.php new file mode 100644 index 000000000..8f8a158ca --- /dev/null +++ b/app/Http/Middleware/Server/AuthenticateAsSubuser.php @@ -0,0 +1,68 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Middleware\Server; + +use Closure; +use Illuminate\Http\Request; +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; + +class AuthenticateAsSubuser +{ + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + private $keyProviderService; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + private $session; + + /** + * SubuserAccessAuthenticate constructor. + * + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct(DaemonKeyProviderService $keyProviderService, Session $session) + { + $this->keyProviderService = $keyProviderService; + $this->session = $session; + } + + /** + * Determine if a subuser has permissions to access a server, if so set thier access token. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function handle(Request $request, Closure $next) + { + $server = $request->attributes->get('server'); + + try { + $token = $this->keyProviderService->handle($server, $request->user()); + } catch (RecordNotFoundException $exception) { + throw new AccessDeniedHttpException('This account does not have permission to access this server.'); + } + + $this->session->now('server_data.token', $token); + $request->attributes->set('server_token', $token); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Server/DatabaseBelongsToServer.php b/app/Http/Middleware/Server/DatabaseBelongsToServer.php new file mode 100644 index 000000000..d7e34d211 --- /dev/null +++ b/app/Http/Middleware/Server/DatabaseBelongsToServer.php @@ -0,0 +1,51 @@ +repository = $repository; + } + + /** + * Check if a database being requested belongs to the currently loaded server. + * If it does not, throw a 404 error, otherwise continue on with the request + * and set an attribute with the database. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Request $request, Closure $next) + { + $server = $request->attributes->get('server'); + + $database = $this->repository->find($request->input('database')); + if (is_null($database) || $database->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + $request->attributes->set('database', $database); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php new file mode 100644 index 000000000..26da5f843 --- /dev/null +++ b/app/Http/Middleware/Server/ScheduleBelongsToServer.php @@ -0,0 +1,60 @@ +hashids = $hashids; + $this->repository = $repository; + } + + /** + * Determine if a task is assigned to the active server. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function handle(Request $request, Closure $next) + { + $server = $request->attributes->get('server'); + + $scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0); + $schedule = $this->repository->getScheduleWithTasks($scheduleId); + + if ($schedule->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + $request->attributes->set('schedule', $schedule); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php new file mode 100644 index 000000000..cdcd3f097 --- /dev/null +++ b/app/Http/Middleware/Server/SubuserBelongsToServer.php @@ -0,0 +1,67 @@ +hashids = $hashids; + $this->repository = $repository; + } + + /** + * Determine if a user has permission to access and modify subuser. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function handle(Request $request, Closure $next) + { + $server = $request->attributes->get('server'); + + $hash = $request->route()->parameter('subuser', 0); + $subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0)); + if (is_null($subuser) || $subuser->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + if ($request->method() === 'PATCH') { + if ($subuser->user_id === $request->user()->id) { + throw new DisplayException(trans('exceptions.subusers.editing_self')); + } + } + + $request->attributes->set('subuser', $subuser); + + return $next($request); + } +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php new file mode 100644 index 000000000..f44f1d6eb --- /dev/null +++ b/app/Http/Middleware/TrustProxies.php @@ -0,0 +1,29 @@ + 'FORWARDED', + Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + ]; +} diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index 58c0597aa..7464e854b 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -3,28 +3,47 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use stdClass; +use GuzzleHttp\Client; +use Illuminate\Http\Request; use Pterodactyl\Events\Auth\FailedCaptcha; +use Illuminate\Contracts\Config\Repository; class VerifyReCaptcha { + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * VerifyReCaptcha constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(Repository $config) + { + $this->config = $config; + } + /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return \Illuminate\Http\RediectResponse + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return \Illuminate\Http\RedirectResponse|mixed */ public function handle($request, Closure $next) { - if (! config('recaptcha.enabled')) { + if (! $this->config->get('recaptcha.enabled')) { return $next($request); } - if ($request->has('g-recaptcha-response')) { - $client = new \GuzzleHttp\Client(); - $res = $client->post(config('recaptcha.domain'), [ + if ($request->filled('g-recaptcha-response')) { + $client = new Client(); + $res = $client->post($this->config->get('recaptcha.domain'), [ 'form_params' => [ - 'secret' => config('recaptcha.secret_key'), + 'secret' => $this->config->get('recaptcha.secret_key'), 'response' => $request->input('g-recaptcha-response'), ], ]); @@ -32,29 +51,33 @@ class VerifyReCaptcha if ($res->getStatusCode() === 200) { $result = json_decode($res->getBody()); - $verified = function ($result, $request) { - if (! config('recaptcha.verify_domain')) { - return false; - } - - $url = parse_url($request->url()); - - if (! array_key_exists('host', $url)) { - return false; - } - - return $result->hostname === $url['host']; - }; - - if ($result->success && (! config('recaptcha.verify_domain') || $verified($result, $request))) { + if ($result->success && (! $this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) { return $next($request); } } } // Emit an event and return to the previous view with an error (only the captcha error will be shown!) - event(new FailedCaptcha($request->ip(), (! isset($result->hostname) ?: $result->hostname))); + event(new FailedCaptcha($request->ip(), (! isset($result) ?: object_get($result, 'hostname')))); - return back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + return redirect()->back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + } + + /** + * Determine if the response from the recaptcha servers was valid. + * + * @param stdClass $result + * @param \Illuminate\Http\Request $request + * @return bool + */ + private function isResponseVerified(stdClass $result, Request $request): bool + { + if (! $this->config->get('recaptcha.verify_domain')) { + return false; + } + + $url = parse_url($request->url()); + + return $result->hostname === array_get($url, 'host'); } } diff --git a/app/Http/Requests/Admin/AdminFormRequest.php b/app/Http/Requests/Admin/AdminFormRequest.php new file mode 100644 index 000000000..012d73364 --- /dev/null +++ b/app/Http/Requests/Admin/AdminFormRequest.php @@ -0,0 +1,42 @@ +user())) { + return false; + } + + return (bool) $this->user()->root_admin; + } + + /** + * Return only the fields that we are interested in from the request. + * This will include empty fields as a null value. + * + * @param array|null $only + * @return array + */ + public function normalize(array $only = null) + { + 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 new file mode 100644 index 000000000..372dd115d --- /dev/null +++ b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php @@ -0,0 +1,39 @@ +mapWithKeys(function ($resource) use ($modelRules) { + return [AdminAcl::COLUMN_IDENTIFER . $resource => $modelRules['r_' . $resource]]; + })->merge(['memo' => $modelRules['memo']])->toArray(); + } + + /** + * @return array + */ + public function attributes() + { + return [ + 'memo' => 'Description', + ]; + } + + public function getKeyPermissions(): array + { + return collect($this->validated())->filter(function ($value, $key) { + return substr($key, 0, strlen(AdminAcl::COLUMN_IDENTIFER)) === AdminAcl::COLUMN_IDENTIFER; + })->toArray(); + } +} diff --git a/app/Http/Requests/Admin/BaseFormRequest.php b/app/Http/Requests/Admin/BaseFormRequest.php new file mode 100644 index 000000000..dff6b9fb2 --- /dev/null +++ b/app/Http/Requests/Admin/BaseFormRequest.php @@ -0,0 +1,20 @@ +. + * + * 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() + { + return [ + 'company' => 'required|between:1,256', + ]; + } +} diff --git a/app/Http/Requests/Admin/DatabaseHostFormRequest.php b/app/Http/Requests/Admin/DatabaseHostFormRequest.php new file mode 100644 index 000000000..ae46f7c76 --- /dev/null +++ b/app/Http/Requests/Admin/DatabaseHostFormRequest.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\DatabaseHost; + +class DatabaseHostFormRequest extends AdminFormRequest +{ + /** + * @return mixed + */ + public function rules() + { + if (! $this->filled('node_id')) { + $this->merge(['node_id' => null]); + } + + if ($this->method() !== 'POST') { + return DatabaseHost::getUpdateRulesForId($this->route()->parameter('host')); + } + + return DatabaseHost::getCreateRules(); + } +} diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php new file mode 100644 index 000000000..539ee3adc --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -0,0 +1,49 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Egg; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EggFormRequest extends AdminFormRequest +{ + /** + * {@inheritdoc} + */ + public function rules() + { + $rules = [ + 'name' => 'required|string|max:255', + 'description' => 'required|string', + 'docker_image' => 'required|string|max:255', + 'startup' => 'required|string', + 'config_from' => 'sometimes|bail|nullable|numeric', + 'config_stop' => 'required_without:config_from|nullable|string|max:255', + 'config_startup' => 'required_without:config_from|nullable|json', + 'config_logs' => 'required_without:config_from|nullable|json', + 'config_files' => 'required_without:config_from|nullable|json', + ]; + + if ($this->method() === 'POST') { + $rules['nest_id'] = 'required|numeric|exists:nests,id'; + } + + return $rules; + } + + /** + * @param \Illuminate\Contracts\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->sometimes('config_from', 'exists:eggs,id', function () { + return (int) $this->input('config_from') !== 0; + }); + } +} diff --git a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php new file mode 100644 index 000000000..b6adb768e --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Egg; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EggImportFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + $rules = [ + 'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain', + ]; + + if ($this->method() !== 'PUT') { + $rules['import_to_nest'] = 'bail|required|integer|exists:nests,id'; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php new file mode 100644 index 000000000..3f522e96f --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Egg; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EggScriptFormRequest extends AdminFormRequest +{ + /** + * Return the rules to be used when validating the sent data in the request. + * + * @return array + */ + public function rules() + { + return [ + 'script_install' => 'sometimes|nullable|string', + 'script_is_privileged' => 'sometimes|required|boolean', + 'script_entry' => 'sometimes|required|string', + 'script_container' => 'sometimes|required|string', + 'copy_script_from' => 'sometimes|nullable|numeric', + ]; + } +} diff --git a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php new file mode 100644 index 000000000..933bf8348 --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -0,0 +1,26 @@ + 'required|string|min:1|max:255', + 'description' => 'sometimes|nullable|string', + 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + 'options' => 'sometimes|required|array', + 'rules' => 'bail|required|string', + 'default_value' => 'present', + ]; + } +} diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php new file mode 100644 index 000000000..16d80a253 --- /dev/null +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -0,0 +1,29 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\Location; + +class LocationFormRequest extends AdminFormRequest +{ + /** + * Setup the validation rules to use for these requests. + * + * @return array + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return Location::getUpdateRulesForId($this->route()->parameter('location')->id); + } + + return Location::getCreateRules(); + } +} diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php new file mode 100644 index 000000000..56255e558 --- /dev/null +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -0,0 +1,26 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Nest; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class StoreNestFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'required|nullable|string', + ]; + } +} diff --git a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php new file mode 100644 index 000000000..2552114ab --- /dev/null +++ b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php @@ -0,0 +1,26 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class AllocationAliasFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'alias' => 'present|nullable|string', + 'allocation_id' => 'required|numeric|exists:allocations,id', + ]; + } +} diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php new file mode 100644 index 000000000..777d3033f --- /dev/null +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -0,0 +1,27 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class AllocationFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'allocation_ip' => 'required|string', + 'allocation_alias' => 'sometimes|nullable|string|max:255', + 'allocation_ports' => 'required|array', + ]; + } +} diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php new file mode 100644 index 000000000..fcedd4275 --- /dev/null +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -0,0 +1,48 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Models\Node; +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class NodeFormRequest extends AdminFormRequest +{ + /** + * Get rules to apply to data in this request. + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return Node::getUpdateRulesForId($this->route()->parameter('node')->id); + } + + return Node::getCreateRules(); + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + // Check that the FQDN is a valid IP address. + if (! filter_var(gethostbyname($this->input('fqdn')), FILTER_VALIDATE_IP)) { + $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_not_resolvable')); + } + + // Check that if using HTTPS the FQDN is not an IP address. + if (filter_var($this->input('fqdn'), FILTER_VALIDATE_IP) && $this->input('scheme') === 'https') { + $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_required_for_ssl')); + } + }); + } +} diff --git a/app/Http/Requests/Admin/PackFormRequest.php b/app/Http/Requests/Admin/PackFormRequest.php new file mode 100644 index 000000000..7f15be958 --- /dev/null +++ b/app/Http/Requests/Admin/PackFormRequest.php @@ -0,0 +1,49 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\Pack; +use Pterodactyl\Services\Packs\PackCreationService; + +class PackFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return Pack::getUpdateRulesForId($this->route()->parameter('pack')->id); + } + + return Pack::getCreateRules(); + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + if ($this->method() !== 'POST') { + return; + } + + $validator->after(function ($validator) { + $mimetypes = implode(',', PackCreationService::VALID_UPLOAD_TYPES); + + /* @var $validator \Illuminate\Validation\Validator */ + $validator->sometimes('file_upload', 'sometimes|required|file|mimetypes:' . $mimetypes, function () { + return true; + }); + }); + } +} diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php new file mode 100644 index 000000000..33b9c8ffd --- /dev/null +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -0,0 +1,76 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\Server; +use Illuminate\Validation\Rule; + +class ServerFormRequest extends AdminFormRequest +{ + /** + * Rules to be applied to this request. + * + * @return array + */ + public function rules() + { + $rules = Server::getCreateRules(); + $rules['description'][] = 'nullable'; + + return $rules; + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + $validator->sometimes('node_id', 'required|numeric|bail|exists:nodes,id', function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('allocation_id', [ + 'required', + 'numeric', + 'bail', + Rule::exists('allocations', 'id')->where(function ($query) { + $query->where('node_id', $this->input('node_id')); + $query->whereNull('server_id'); + }), + ], function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('allocation_additional.*', [ + 'sometimes', + 'required', + 'numeric', + Rule::exists('allocations', 'id')->where(function ($query) { + $query->where('node_id', $this->input('node_id')); + $query->whereNull('server_id'); + }), + ], function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('pack_id', [ + Rule::exists('packs', 'id')->where(function ($query) { + $query->where('selectable', 1); + $query->where('egg_id', $this->input('egg_id')); + }), + ], function ($input) { + return $input->pack_id !== 0 && $input->pack_id !== null; + }); + }); + } +} diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php new file mode 100644 index 000000000..a80d8dab9 --- /dev/null +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -0,0 +1,42 @@ + 'required|in:true,false', + 'recaptcha:secret_key' => 'required|string|max:255', + 'recaptcha:website_key' => 'required|string|max:255', + 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', + 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', + 'pterodactyl:console:count' => 'required|integer|min:1', + 'pterodactyl:console:frequency' => 'required|integer|min:10', + ]; + } + + /** + * @return array + */ + public function attributes() + { + return [ + 'recaptcha:enabled' => 'reCAPTCHA Enabled', + 'recaptcha:secret_key' => 'reCAPTCHA Secret Key', + 'recaptcha:website_key' => 'reCAPTCHA Website Key', + 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', + 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', + 'pterodactyl:console:count' => 'Console Message Count', + 'pterodactyl:console:frequency' => 'Console Frequency Tick', + ]; + } +} diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php new file mode 100644 index 000000000..0b02561dd --- /dev/null +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -0,0 +1,36 @@ + 'required|string|max:255', + 'pterodactyl:auth:2fa_required' => 'required|integer|in:0,1,2', + 'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))], + ]; + } + + /** + * @return array + */ + public function attributes() + { + return [ + 'app:name' => 'Company Name', + 'pterodactyl:auth:2fa_required' => 'Require 2-Factor Authentication', + 'app:locale' => 'Default Language', + ]; + } +} diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php new file mode 100644 index 000000000..5a2e2ee8e --- /dev/null +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -0,0 +1,45 @@ + 'required|string', + 'mail:port' => 'required|integer|between:1,65535', + 'mail:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])], + 'mail:username' => 'nullable|string|max:255', + 'mail:password' => 'nullable|string|max:255', + 'mail:from:address' => 'required|string|email', + 'mail:from:name' => 'nullable|string|max:255', + ]; + } + + /** + * 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) + { + $keys = array_flip(array_keys($this->rules())); + + if (empty($this->input('mail:password'))) { + unset($keys['mail:password']); + } + + return $this->only(array_flip($keys)); + } +} diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php new file mode 100644 index 000000000..c6a358398 --- /dev/null +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -0,0 +1,27 @@ +method() === 'PATCH') { + $rules = collect(User::getUpdateRulesForId($this->route()->parameter('user')->id))->merge([ + 'ignore_connection_error' => ['sometimes', 'nullable', 'boolean'], + ]); + } + + return $rules->only([ + 'email', 'username', 'name_first', 'name_last', 'password', + 'language', 'ignore_connection_error', 'root_admin', + ])->toArray(); + } +} diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php new file mode 100644 index 000000000..21da6bfb3 --- /dev/null +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -0,0 +1,41 @@ +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 new file mode 100644 index 000000000..150bdb95e --- /dev/null +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -0,0 +1,33 @@ +route()->parameter('node'); + + return $node instanceof Node && $node->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php new file mode 100644 index 000000000..2cf2ae004 --- /dev/null +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -0,0 +1,46 @@ + 'required|string', + 'alias' => 'sometimes|nullable|string|max:255', + 'ports' => 'required|array', + 'ports.*' => 'string', + ]; + } + + /** + * @return array + */ + public function validated() + { + $data = parent::validated(); + + return [ + 'allocation_ip' => $data['ip'], + 'allocation_ports' => $data['ports'], + 'allocation_alias' => $data['alias'], + ]; + } +} diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php new file mode 100644 index 000000000..126e6d604 --- /dev/null +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -0,0 +1,125 @@ +resource)) { + 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. + * + * @return bool + */ + public function resourceExists(): bool + { + return true; + } + + /** + * Default set of rules to apply to API requests. + * + * @return array + */ + public function rules(): array + { + return []; + } + + /** + * Return the API key being used for the request. + * + * @return \Pterodactyl\Models\ApiKey + */ + public function key(): ApiKey + { + return $this->attributes->get('api_key'); + } + + /** + * Grab a model from the route parameters. If no model exists under + * the specified key a default response is returned. + * + * @param string $model + * @param mixed $default + * @return mixed + */ + public function getModel(string $model, $default = null) + { + $parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model); + + if (! is_null($parameterKey)) { + $model = $this->route()->parameter($parameterKey); + } + + return $model ?? $default; + } + + /* + * Determine if the request passes the authorization check as well + * as the exists check. + * + * @return bool + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + + /** + * @return bool + */ + protected function passesAuthorization() + { + 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')); + } + + return true; + } +} diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php new file mode 100644 index 000000000..d1863eea7 --- /dev/null +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -0,0 +1,32 @@ +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 new file mode 100644 index 000000000..fa6c67a86 --- /dev/null +++ b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php @@ -0,0 +1,21 @@ +route()->parameter('location'); + + return $location instanceof Location && $location->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php new file mode 100644 index 000000000..5edf00462 --- /dev/null +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -0,0 +1,19 @@ +only([ + 'long', + 'short', + ])->toArray(); + } + + /** + * Rename fields to be more clear in error messages. + * + * @return array + */ + public function attributes() + { + return [ + 'long' => 'Location Description', + 'short' => 'Location Identifier', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php new file mode 100644 index 000000000..c65a6f904 --- /dev/null +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -0,0 +1,36 @@ +route()->parameter('location'); + + return $location instanceof Location && $location->exists; + } + + /** + * Rules to validate this request aganist. + * + * @return array + */ + public function rules(): array + { + $locationId = $this->route()->parameter('location')->id; + + return collect(Location::getUpdateRulesForId($locationId))->only([ + 'short', + 'long', + ]); + } +} diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php new file mode 100644 index 000000000..b3f1a08a0 --- /dev/null +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php @@ -0,0 +1,29 @@ +getModel('nest')->id === $this->getModel('egg')->nest_id; + } +} diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php new file mode 100644 index 000000000..a6aadf904 --- /dev/null +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php @@ -0,0 +1,19 @@ +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 new file mode 100644 index 000000000..fbf957edd --- /dev/null +++ b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php @@ -0,0 +1,20 @@ +route()->parameter('node'); + + return $node instanceof Node && $node->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php new file mode 100644 index 000000000..fc5f5a38e --- /dev/null +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -0,0 +1,19 @@ +only([ + 'public', + 'name', + 'location_id', + 'fqdn', + 'scheme', + 'behind_proxy', + 'memory', + 'memory_overallocate', + 'disk', + 'disk_overallocation', + 'upload_size', + 'daemonListen', + 'daemonSFTP', + 'daemonBase', + ])->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() + { + return [ + 'daemon_base' => 'Daemon Base Path', + 'upload_size' => 'File Upload Size Limit', + 'location_id' => 'Location', + 'public' => 'Node Visibility', + ]; + } + + /** + * 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() + { + $response = parent::validated(); + $response['daemonListen'] = $response['daemon_listen']; + $response['daemonSFTP'] = $response['daemon_sftp']; + $response['daemonBase'] = $response['daemon_base']; + + unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']); + + return $response; + } +} diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php new file mode 100644 index 000000000..ffba39e6d --- /dev/null +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -0,0 +1,22 @@ +getModel(Node::class)->id; + + return parent::rules(Node::getUpdateRulesForId($nodeId)); + } +} diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php new file mode 100644 index 000000000..e398a5bbf --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -0,0 +1,32 @@ +route()->parameter('server'); + $database = $this->route()->parameter('database'); + + return $database->server_id === $server->id; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php new file mode 100644 index 000000000..3e6cfc6fe --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -0,0 +1,19 @@ + 'required|string|min:1|max:24', + 'remote' => 'required|string|min:1', + 'host' => 'required|integer|exists:database_hosts,id', + ]; + } + + /** + * Return data formatted in the correct format for the service to consume. + * + * @return array + */ + public function validated() + { + return [ + 'database' => $this->input('database'), + 'remote' => $this->input('remote'), + 'database_host_id' => $this->input('host'), + ]; + } + + /** + * Format error messages in a more understandable format for API output. + * + * @return array + */ + public function attributes() + { + return [ + 'host' => 'Database Host Server ID', + 'remote' => 'Remote Connection String', + 'database' => 'Database Name', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/GetServersRequest.php b/app/Http/Requests/Api/Application/Servers/GetServersRequest.php new file mode 100644 index 000000000..922610385 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/GetServersRequest.php @@ -0,0 +1,29 @@ + 'string|max:100', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php new file mode 100644 index 000000000..728b1ce52 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -0,0 +1,32 @@ +route()->parameter('server'); + + return $server instanceof Server && $server->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php new file mode 100644 index 000000000..ff389e05e --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -0,0 +1,150 @@ + $rules['name'], + 'description' => array_merge(['nullable'], $rules['description']), + 'user' => $rules['owner_id'], + 'egg' => $rules['egg_id'], + 'pack' => $rules['pack_id'], + 'docker_image' => $rules['image'], + 'startup' => $rules['startup'], + 'environment' => 'required|array', + 'skip_scripts' => 'sometimes|boolean', + + // Resource limitations + 'limits' => 'required|array', + 'limits.memory' => $rules['memory'], + 'limits.swap' => $rules['swap'], + 'limits.disk' => $rules['disk'], + 'limits.io' => $rules['io'], + 'limits.cpu' => $rules['cpu'], + + // 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', + + 'start_on_completion' => 'sometimes|boolean', + ]; + } + + /** + * Normalize the data into a format that can be consumed by the service. + * + * @return array + */ + public function validated() + { + $data = parent::validated(); + + return [ + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + 'owner_id' => array_get($data, 'user'), + 'egg_id' => array_get($data, 'egg'), + 'pack_id' => array_get($data, 'pack'), + 'image' => array_get($data, 'docker_image'), + 'startup' => array_get($data, 'startup'), + 'environment' => array_get($data, 'environment'), + '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'), + '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), + ]; + } + + /* + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + */ + public function withValidator(Validator $validator) + { + $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; + }); + } + + /** + * Return a deployment object that can be passed to the server creation service. + * + * @return \Pterodactyl\Models\Objects\DeploymentObject|null + */ + public function getDeploymentObject() + { + if (is_null($this->input('deploy'))) { + return null; + } + + $object = new DeploymentObject; + $object->setDedicated($this->input('deploy.dedicated_ip', false)); + $object->setLocations($this->input('deploy.locations', [])); + $object->setPorts($this->input('deploy.port_range', [])); + + return $object; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php new file mode 100644 index 000000000..893ff5ff7 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -0,0 +1,61 @@ +route()->parameter('server')->id); + + return [ + 'allocation' => $rules['allocation_id'], + 'memory' => $rules['memory'], + 'swap' => $rules['swap'], + 'io' => $rules['io'], + 'cpu' => $rules['cpu'], + 'disk' => $rules['disk'], + 'add_allocations' => 'bail|array', + 'add_allocations.*' => 'integer', + 'remove_allocations' => 'bail|array', + 'remove_allocations.*' => 'integer', + ]; + } + + /** + * Convert the allocation field into the expected format for the service handler. + * + * @return array + */ + public function validated() + { + $data = parent::validated(); + + $data['allocation_id'] = $data['allocation']; + unset($data['allocation']); + + return $data; + } + + /** + * Custom attributes to use in error message responses. + * + * @return array + */ + public function attributes() + { + return [ + 'add_allocations' => 'allocations to add', + 'remove_allocations' => 'allocations to remove', + 'add_allocations.*' => 'allocation to add', + 'remove_allocations.*' => 'allocation to remove', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php new file mode 100644 index 000000000..4b75138b9 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -0,0 +1,53 @@ +route()->parameter('server')->id); + + return [ + 'name' => $rules['name'], + 'user' => $rules['owner_id'], + 'description' => array_merge(['nullable'], $rules['description']), + ]; + } + + /** + * Convert the posted data into the correct format that is expected + * by the application. + * + * @return array + */ + public function validated(): array + { + return [ + 'name' => $this->input('name'), + 'owner_id' => $this->input('user'), + 'description' => $this->input('description'), + ]; + } + + /** + * Rename some of the attributes in error messages to clarify the field + * being discussed. + * + * @return array + */ + public function attributes(): array + { + return [ + 'user' => 'User ID', + 'name' => 'Server Name', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php new file mode 100644 index 000000000..d337cb4dd --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -0,0 +1,55 @@ +getModel(Server::class)->id); + + return [ + 'startup' => $data['startup'], + 'environment' => 'present|array', + 'egg' => $data['egg_id'], + 'pack' => $data['pack_id'], + 'image' => $data['image'], + 'skip_scripts' => 'present|boolean', + ]; + } + + /** + * Return the validated data in a format that is expected by the service. + * + * @return array + */ + public function validated() + { + $data = parent::validated(); + + return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([ + 'egg_id' => array_get($data, 'egg'), + 'pack_id' => array_get($data, 'pack'), + 'docker_image' => array_get($data, 'image'), + ])->toArray(); + } +} diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php new file mode 100644 index 000000000..571b29c63 --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -0,0 +1,32 @@ +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 new file mode 100644 index 000000000..b1f779183 --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -0,0 +1,56 @@ +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. + * @return \Pterodactyl\Models\User + */ + public function getUserModel(): User + { + return $this->userModel; + } +} diff --git a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php new file mode 100644 index 000000000..8736a8e9d --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php @@ -0,0 +1,19 @@ +only([ + 'external_id', + 'email', + 'username', + 'password', + 'language', + 'root_admin', + ])->toArray(); + + $response['first_name'] = $rules['name_first']; + $response['last_name'] = $rules['name_last']; + + return $response; + } + + /** + * @return array + */ + public function validated() + { + $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. + * + * @return array + */ + public function attributes() + { + 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 new file mode 100644 index 000000000..929a77f32 --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -0,0 +1,21 @@ +getModel(User::class)->id; + + return parent::rules(User::getUpdateRulesForId($userId)); + } +} diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php new file mode 100644 index 000000000..041ff197f --- /dev/null +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -0,0 +1,44 @@ + 'required|string', + 'password' => 'required|string', + ]; + } + + /** + * 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() + { + return $this->only( + array_keys($this->rules()) + ); + } +} diff --git a/app/Http/Requests/Base/AccountDataFormRequest.php b/app/Http/Requests/Base/AccountDataFormRequest.php new file mode 100644 index 000000000..0d24b7a16 --- /dev/null +++ b/app/Http/Requests/Base/AccountDataFormRequest.php @@ -0,0 +1,70 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Base; + +use Pterodactyl\Models\User; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; +use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; + +class AccountDataFormRequest extends FrontendUserFormRequest +{ + /** + * @return bool + * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException + */ + public function authorize() + { + if (! parent::authorize()) { + return false; + } + + // Verify password matches when changing password or email. + if (in_array($this->input('do_action'), ['password', 'email'])) { + if (! password_verify($this->input('current_password'), $this->user()->password)) { + throw new InvalidPasswordProvidedException(trans('base.account.invalid_password')); + } + } + + return true; + } + + /** + * @return array + */ + public function rules() + { + $modelRules = User::getUpdateRulesForId($this->user()->id); + + switch ($this->input('do_action')) { + case 'email': + $rules = [ + 'new_email' => array_get($modelRules, 'email'), + ]; + break; + case 'password': + $rules = [ + 'new_password' => 'required|confirmed|string|min:8', + 'new_password_confirmation' => 'required', + ]; + break; + case 'identity': + $rules = [ + 'name_first' => array_get($modelRules, 'name_first'), + 'name_last' => array_get($modelRules, 'name_last'), + 'username' => array_get($modelRules, 'username'), + ]; + break; + default: + abort(422); + } + + return $rules; + } +} diff --git a/app/Http/Requests/Base/ApiKeyFormRequest.php b/app/Http/Requests/Base/ApiKeyFormRequest.php new file mode 100644 index 000000000..5959657f0 --- /dev/null +++ b/app/Http/Requests/Base/ApiKeyFormRequest.php @@ -0,0 +1,74 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Base; + +use IPTools\Network; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; + +class ApiKeyFormRequest extends FrontendUserFormRequest +{ + /** + * Rules applied to data passed in this request. + * + * @return array + */ + public function rules() + { + $this->parseAllowedIntoArray(); + + return [ + 'memo' => 'required|nullable|string|max:500', + 'permissions' => 'sometimes|present|array', + 'admin_permissions' => 'sometimes|present|array', + 'allowed_ips' => 'present', + 'allowed_ips.*' => 'sometimes|string', + ]; + } + + /** + * Parse the string of allowed IPs into an array. + */ + protected function parseAllowedIntoArray() + { + $loop = []; + if (! empty($this->input('allowed_ips'))) { + foreach (explode(PHP_EOL, $this->input('allowed_ips')) as $ip) { + $loop[] = trim($ip); + } + } + + $this->merge(['allowed_ips' => $loop]); + } + + /** + * Run additional validation rules on the request to ensure all of the data is good. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + /* @var \Illuminate\Validation\Validator $validator */ + if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) { + $validator->errors()->add('permissions', 'At least one permission must be selected.'); + } + + foreach ($this->input('allowed_ips') as $ip) { + $ip = trim($ip); + + try { + Network::parse($ip); + } catch (\Exception $ex) { + $validator->errors()->add('allowed_ips', 'Could not parse IP ' . $ip . ' because it is in an invalid format.'); + } + } + }); + } +} diff --git a/app/Http/Requests/Base/StoreAccountKeyRequest.php b/app/Http/Requests/Base/StoreAccountKeyRequest.php new file mode 100644 index 000000000..ab3906636 --- /dev/null +++ b/app/Http/Requests/Base/StoreAccountKeyRequest.php @@ -0,0 +1,23 @@ + 'required|nullable|string|max:500', + 'allowed_ips' => 'present', + 'allowed_ips.*' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/FrontendUserFormRequest.php b/app/Http/Requests/FrontendUserFormRequest.php new file mode 100644 index 000000000..6be818e7b --- /dev/null +++ b/app/Http/Requests/FrontendUserFormRequest.php @@ -0,0 +1,40 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests; + +use Illuminate\Foundation\Http\FormRequest; + +abstract class FrontendUserFormRequest extends FormRequest +{ + abstract public function rules(); + + /** + * Determine if a user is authorized to access this endpoint. + * + * @return bool + */ + public function authorize() + { + return ! is_null($this->user()); + } + + /** + * 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() + { + return $this->only( + array_keys($this->rules()) + ); + } +} diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php deleted file mode 100644 index 5e3ae0bc5..000000000 --- a/app/Http/Requests/Request.php +++ /dev/null @@ -1,10 +0,0 @@ -. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Server; + +class ScheduleCreationFormRequest extends ServerFormRequest +{ + /** + * Permission to validate this request aganist. + * + * @return string + */ + protected function permission(): string + { + if ($this->method() === 'PATCH') { + return 'edit-schedule'; + } + + return 'create-schedule'; + } + + /** + * Validation rules to apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'name' => 'string|max:255', + 'cron_day_of_week' => 'required|string', + 'cron_day_of_month' => 'required|string', + 'cron_hour' => 'required|string', + 'cron_minute' => 'required|string', + 'tasks' => 'sometimes|array|size:4', + 'tasks.time_value' => 'required_with:tasks|max:5', + 'tasks.time_interval' => 'required_with:tasks|max:5', + 'tasks.action' => 'required_with:tasks|max:5', + 'tasks.payload' => 'required_with:tasks|max:5', + 'tasks.time_value.*' => 'numeric|between:0,59', + 'tasks.time_interval.*' => 'string|in:s,m', + 'tasks.action.*' => 'string|in:power,command', + 'tasks.payload.*' => 'string', + ]; + } + + /** + * Normalize the request into a format that can be used by the application. + * + * @return array + */ + public function normalize() + { + return $this->only('name', 'cron_day_of_week', 'cron_day_of_month', 'cron_hour', 'cron_minute'); + } + + /** + * Return the tasks provided in the request that are associated with this schedule. + * + * @return array|null + */ + public function getTasks() + { + $restructured = []; + foreach (array_get($this->all(), 'tasks', []) as $key => $values) { + for ($i = 0; $i < count($values); $i++) { + $restructured[$i][$key] = $values[$i]; + } + } + + return empty($restructured) ? null : $restructured; + } +} diff --git a/app/Http/Requests/Server/ServerFormRequest.php b/app/Http/Requests/Server/ServerFormRequest.php new file mode 100644 index 000000000..b796a21e0 --- /dev/null +++ b/app/Http/Requests/Server/ServerFormRequest.php @@ -0,0 +1,29 @@ +user()->can($this->permission(), $this->attributes->get('server')); + } +} diff --git a/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php new file mode 100644 index 000000000..861732e81 --- /dev/null +++ b/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php @@ -0,0 +1,31 @@ + 'required|email', + 'permissions' => 'present|array', + ]; + } +} diff --git a/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php new file mode 100644 index 000000000..284b87553 --- /dev/null +++ b/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php @@ -0,0 +1,30 @@ + 'present|array', + ]; + } +} diff --git a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php new file mode 100644 index 000000000..0ebaa25b6 --- /dev/null +++ b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php @@ -0,0 +1,101 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Server; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; + +class UpdateFileContentsFormRequest extends ServerFormRequest +{ + /** + * Return the permission string to validate this request aganist. + * + * @return string + */ + protected function permission(): string + { + return 'edit-files'; + } + + /** + * Authorize a request to edit a file. + * + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function authorize() + { + if (! parent::authorize()) { + return false; + } + + $server = $this->attributes->get('server'); + $token = $this->attributes->get('server_token'); + + return $this->checkFileCanBeEdited($server, $token); + } + + /** + * @return array + */ + public function rules() + { + return []; + } + + /** + * Checks if a given file can be edited by a user on this server. + * + * @param \Pterodactyl\Models\Server $server + * @param string $token + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + */ + private function checkFileCanBeEdited($server, $token) + { + $config = app()->make(Repository::class); + $repository = app()->make(FileRepositoryInterface::class); + + try { + $stats = $repository->setServer($server)->setToken($token)->getFileStat($this->route()->parameter('file')); + } catch (RequestException $exception) { + switch ($exception->getCode()) { + case 404: + throw new NotFoundHttpException; + default: + throw new DaemonConnectionException($exception); + } + } + + if ((! $stats->file && ! $stats->symlink) || ! in_array($stats->mime, $config->get('pterodactyl.files.editable'))) { + throw new FileTypeNotEditableException(trans('server.files.exceptions.invalid_mime')); + } + + if ($stats->size > $config->get('pterodactyl.files.max_edit_size')) { + throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size')); + } + + $this->attributes->set('file_stats', $stats); + + return true; + } +} diff --git a/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php b/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php new file mode 100644 index 000000000..41c15103f --- /dev/null +++ b/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php @@ -0,0 +1,61 @@ +user()->can('edit-startup', $this->attributes->get('server')); + } + + /** + * Validate that all of the required fields were passed and that the environment + * variable values meet the defined criteria for those fields. + * + * @return array + */ + public function rules() + { + $repository = $this->container->make(EggVariableRepositoryInterface::class); + + $variables = $repository->getEditableVariables($this->attributes->get('server')->egg_id); + $rules = $variables->mapWithKeys(function ($variable) { + $this->validationAttributes['environment.' . $variable->env_variable] = $variable->name; + + return ['environment.' . $variable->env_variable => $variable->rules]; + })->toArray(); + + return array_merge($rules, [ + 'environment' => 'required|array', + ]); + } + + /** + * Return attributes to provide better naming conventions for error messages. + * + * @return array + */ + public function attributes() + { + return $this->validationAttributes; + } +} diff --git a/app/Http/ViewComposers/Server/ServerDataComposer.php b/app/Http/ViewComposers/Server/ServerDataComposer.php new file mode 100644 index 000000000..9e1858645 --- /dev/null +++ b/app/Http/ViewComposers/Server/ServerDataComposer.php @@ -0,0 +1,38 @@ +request = $request; + } + + /** + * Attach server data to a view automatically. + * + * @param \Illuminate\View\View $view + */ + public function compose(View $view) + { + $server = $this->request->get('server'); + + $view->with('server', $server); + $view->with('node', object_get($server, 'node')); + $view->with('daemon_token', $this->request->get('server_token')); + } +} diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php new file mode 100644 index 000000000..a00052b1a --- /dev/null +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -0,0 +1,176 @@ +queue = config('pterodactyl.queues.standard'); + $this->task = $task; + $this->schedule = $schedule; + } + + /** + * Run the job and send actions to the daemon running the server. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface $commandRepository + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService + * @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle( + CommandRepositoryInterface $commandRepository, + DaemonKeyProviderService $keyProviderService, + PowerRepositoryInterface $powerRepository, + TaskRepositoryInterface $taskRepository + ) { + $this->commandRepository = $commandRepository; + $this->powerRepository = $powerRepository; + $this->taskRepository = $taskRepository; + + $task = $this->taskRepository->getTaskForJobProcess($this->task); + $server = $task->getRelation('server'); + $user = $server->getRelation('user'); + + // Do not process a task that is not set to active. + if (! $task->getRelation('schedule')->is_active) { + $this->markTaskNotQueued(); + $this->markScheduleComplete(); + + return; + } + + // Perform the provided task aganist the daemon. + switch ($task->action) { + case 'power': + $this->powerRepository->setServer($server) + ->setToken($keyProviderService->handle($server, $user)) + ->sendSignal($task->payload); + break; + case 'command': + $this->commandRepository->setServer($server) + ->setToken($keyProviderService->handle($server, $user)) + ->send($task->payload); + break; + default: + throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); + } + + $this->markTaskNotQueued(); + $this->queueNextTask($task->sequence_id); + } + + /** + * Handle a failure while sending the action to the daemon or otherwise processing the job. + * + * @param null|\Exception $exception + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function failed(Exception $exception = null) + { + $this->markTaskNotQueued(); + $this->markScheduleComplete(); + } + + /** + * Get the next task in the schedule and queue it for running after the defined period of wait time. + * + * @param int $sequence + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function queueNextTask($sequence) + { + $nextTask = $this->taskRepository->getNextTask($this->schedule, $sequence); + if (is_null($nextTask)) { + $this->markScheduleComplete(); + + return; + } + + $this->taskRepository->update($nextTask->id, ['is_queued' => true]); + $this->dispatch((new self($nextTask->id, $this->schedule))->delay($nextTask->time_offset)); + } + + /** + * Marks the parent schedule as being complete. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function markScheduleComplete() + { + $repository = app()->make(ScheduleRepositoryInterface::class); + $repository->withoutFreshModel()->update($this->schedule, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ]); + } + + /** + * Mark a specific task as no longer being queued. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function markTaskNotQueued() + { + $repository = app()->make(TaskRepositoryInterface::class); + $repository->update($this->task, ['is_queued' => false]); + } +} diff --git a/app/Jobs/SendScheduledTask.php b/app/Jobs/SendScheduledTask.php deleted file mode 100644 index 525b670df..000000000 --- a/app/Jobs/SendScheduledTask.php +++ /dev/null @@ -1,116 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Jobs; - -use Cron; -use Carbon; -use Pterodactyl\Models\Task; -use Pterodactyl\Models\TaskLog; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Contracts\Queue\ShouldQueue; -use Pterodactyl\Repositories\Daemon\PowerRepository; -use Pterodactyl\Repositories\Daemon\CommandRepository; - -class SendScheduledTask extends Job implements ShouldQueue -{ - use InteractsWithQueue, SerializesModels; - - /** - * @var \Pterodactyl\Models\Task - */ - protected $task; - - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Task $task) - { - $this->task = $task; - - $this->task->queued = true; - $this->task->save(); - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $time = Carbon::now(); - $log = new TaskLog; - - if ($this->attempts() >= 1) { - // Just delete the job, we will attempt it again later anyways. - $this->delete(); - } - - try { - if ($this->task->action === 'command') { - $repo = new CommandRepository($this->task->server, $this->task->user); - $response = $repo->send($this->task->data); - } elseif ($this->task->action === 'power') { - $repo = new PowerRepository($this->task->server, $this->task->user); - $response = $repo->do($this->task->data); - } else { - throw new \Exception('Task type provided was not valid.'); - } - - $log->fill([ - 'task_id' => $this->task->id, - 'run_time' => $time, - 'run_status' => 0, - 'response' => $response, - ]); - } catch (\Exception $ex) { - $log->fill([ - 'task_id' => $this->task->id, - 'run_time' => $time, - 'run_status' => 1, - 'response' => $ex->getMessage(), - ]); - } finally { - $cron = Cron::factory(sprintf('%s %s %s %s %s %s', - $this->task->minute, - $this->task->hour, - $this->task->day_of_month, - $this->task->month, - $this->task->day_of_week, - $this->task->year - )); - $this->task->fill([ - 'last_run' => $time, - 'next_run' => $cron->getNextRunDate(), - 'queued' => false, - ]); - $this->task->save(); - $log->save(); - } - } -} diff --git a/app/Models/APIKey.php b/app/Models/APIKey.php deleted file mode 100644 index 6ed73b7c2..000000000 --- a/app/Models/APIKey.php +++ /dev/null @@ -1,77 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class APIKey extends Model -{ - /** - * Public key defined length used in verification methods. - * - * @var int - */ - const PUBLIC_KEY_LEN = 16; - - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'api_keys'; - - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = ['secret']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'allowed_ips' => 'json', - ]; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id', 'created_at', 'updated_at']; - - /** - * Gets the permissions associated with a key. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function permissions() - { - return $this->hasMany(APIPermission::class, 'key_id'); - } -} diff --git a/app/Models/APILog.php b/app/Models/APILog.php index 6d5f26fda..359daa4ed 100644 --- a/app/Models/APILog.php +++ b/app/Models/APILog.php @@ -1,26 +1,4 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; diff --git a/app/Models/APIPermission.php b/app/Models/APIPermission.php deleted file mode 100644 index bfe2fc908..000000000 --- a/app/Models/APIPermission.php +++ /dev/null @@ -1,131 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class APIPermission extends Model -{ - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'api_permissions'; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'key_id' => 'integer', - ]; - - /** - * Disable timestamps for this table. - * - * @var bool - */ - public $timestamps = false; - - /** - * List of permissions available for the API. - * - * @var array - */ - protected static $permissions = [ - // Items within this block are available to non-adminitrative users. - '_user' => [ - 'server' => [ - 'list', - 'view', - 'power', - 'command', - ], - ], - - // All other pemissions below are administrative actions. - 'server' => [ - 'list', - 'create', - 'view', - 'edit-details', - 'edit-container', - 'edit-build', - 'edit-startup', - 'suspend', - 'install', - 'rebuild', - 'delete', - ], - 'location' => [ - 'list', - ], - 'node' => [ - 'list', - 'view', - 'view-config', - 'create', - 'delete', - ], - 'user' => [ - 'list', - 'view', - 'create', - 'edit', - 'delete', - ], - 'service' => [ - 'list', - 'view', - ], - 'option' => [ - 'list', - 'view', - ], - 'pack' => [ - 'list', - 'view', - ], - ]; - - /** - * Return permissions for API. - * - * @return array - */ - public static function permissions() - { - return self::$permissions; - } -} diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index d37e52a0e..5921c0a2b 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -1,33 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Allocation extends Model +class Allocation extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'allocation'; + /** * The table associated with the model. * @@ -42,46 +32,86 @@ class Allocation extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'node_id' => 'integer', - 'port' => 'integer', - 'server_id' => 'integer', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'node_id' => 'integer', + 'port' => 'integer', + 'server_id' => 'integer', + ]; - /** - * Accessor to automatically provide the IP alias if defined. - * - * @param null|string $value - * @return string - */ - public function getAliasAttribute($value) - { - return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; - } + /** + * @var array + */ + protected static $applicationRules = [ + 'node_id' => 'required', + 'ip' => 'required', + 'port' => 'required', + ]; - /** - * Accessor to quickly determine if this allocation has an alias. - * - * @param null|string $value - * @return bool - */ - public function getHasAliasAttribute($value) - { - return ! is_null($this->ip_alias); - } + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'node_id' => 'exists:nodes,id', + 'ip' => 'ip', + 'port' => 'numeric|between:1024,65553', + 'ip_alias' => 'nullable|string', + 'server_id' => 'nullable|exists:servers,id', + ]; - /** - * Gets information for the server associated with this allocation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() - { - return $this->belongsTo(Server::class); - } + /** + * Return a hashid encoded string to represent the ID of the allocation. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + + /** + * Accessor to automatically provide the IP alias if defined. + * + * @param null|string $value + * @return string + */ + public function getAliasAttribute($value) + { + return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; + } + + /** + * Accessor to quickly determine if this allocation has an alias. + * + * @param null|string $value + * @return bool + */ + public function getHasAliasAttribute($value) + { + return ! is_null($this->ip_alias); + } + + /** + * Gets information for the server associated with this allocation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } + + /** + * Return the Node model associated with this allocation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class); + } } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php new file mode 100644 index 000000000..bd05454dd --- /dev/null +++ b/app/Models/ApiKey.php @@ -0,0 +1,130 @@ + 'json', + 'user_id' => 'int', + 'r_' . AdminAcl::RESOURCE_USERS => 'int', + 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', + 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int', + 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int', + 'r_' . AdminAcl::RESOURCE_EGGS => 'int', + 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int', + 'r_' . AdminAcl::RESOURCE_NESTS => 'int', + 'r_' . AdminAcl::RESOURCE_NODES => 'int', + 'r_' . AdminAcl::RESOURCE_PACKS => 'int', + 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', + ]; + + /** + * Fields that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'identifier', + 'token', + 'allowed_ips', + 'memo', + 'last_used_at', + ]; + + /** + * Fields that should not be included when calling toArray() or toJson() + * on this model. + * + * @var array + */ + protected $hidden = ['token']; + + /** + * Rules defining what fields must be passed when making a model. + * + * @var array + */ + protected static $applicationRules = [ + 'identifier' => 'required', + 'memo' => 'required', + 'user_id' => 'required', + 'token' => 'required', + 'key_type' => 'present', + ]; + + /** + * Rules to protect aganist invalid data entry to DB. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'user_id' => 'exists:users,id', + 'key_type' => 'integer|min:0|max:4', + 'identifier' => 'string|size:16|unique:api_keys,identifier', + 'token' => 'string', + 'memo' => 'nullable|string|max:500', + 'allowed_ips' => 'nullable|json', + 'last_used_at' => 'nullable|date', + 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_PACKS => 'integer|min:0|max:3', + 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', + ]; + + /** + * @var array + */ + protected $dates = [ + self::CREATED_AT, + self::UPDATED_AT, + 'last_used_at', + ]; +} diff --git a/app/Models/Checksum.php b/app/Models/Checksum.php deleted file mode 100644 index 6db275624..000000000 --- a/app/Models/Checksum.php +++ /dev/null @@ -1,53 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class Checksum extends Model -{ - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'checksums'; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id', 'created_at', 'updated_at']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'service' => 'integer', - ]; -} diff --git a/app/Models/DaemonKey.php b/app/Models/DaemonKey.php new file mode 100644 index 000000000..c4c2940a9 --- /dev/null +++ b/app/Models/DaemonKey.php @@ -0,0 +1,93 @@ + 'integer', + 'server_id' => 'integer', + ]; + + /** + * @var array + */ + protected $dates = [ + self::CREATED_AT, + self::UPDATED_AT, + 'expires_at', + ]; + + /** + * @var array + */ + protected $fillable = ['user_id', 'server_id', 'secret', 'expires_at']; + + /** + * @var array + */ + protected static $applicationRules = [ + 'user_id' => 'required', + 'server_id' => 'required', + 'secret' => 'required', + 'expires_at' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'user_id' => 'numeric|exists:users,id', + 'server_id' => 'numeric|exists:servers,id', + 'secret' => 'string|min:20', + 'expires_at' => 'date', + ]; + + /** + * Return the server relation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } + + /** + * Return the node relation. + * + * @return \Znck\Eloquent\Relations\BelongsToThrough + * @throws \Exception + */ + public function node() + { + return $this->belongsToThrough(Node::class, Server::class); + } + + /** + * Return the user relation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Database.php b/app/Models/Database.php index 20ad3c1b0..9ff1d8c17 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -1,33 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Database extends Model +class Database extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'server_database'; + /** * The table associated with the model. * @@ -48,7 +38,7 @@ class Database extends Model * @var array */ protected $fillable = [ - 'server_id', 'database_host_id', 'database', 'username', 'remote', + 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', ]; /** @@ -61,6 +51,22 @@ class Database extends Model 'database_host_id' => 'integer', ]; + protected static $applicationRules = [ + 'server_id' => 'required', + 'database_host_id' => 'required', + 'database' => 'required', + 'remote' => 'required', + ]; + + protected static $dataIntegrityRules = [ + 'server_id' => 'numeric|exists:servers,id', + 'database_host_id' => 'exists:database_hosts,id', + 'database' => 'string|alpha_dash|between:3,100', + 'username' => 'string|alpha_dash|between:3,100', + 'remote' => 'string|regex:/^[0-9%.]{1,15}$/', + 'password' => 'string', + ]; + /** * Gets the host database server associated with a database. * diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index 165a99c5a..f42f2650d 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -1,35 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; -use Crypt; -use Config; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class DatabaseHost extends Model +class DatabaseHost extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'database_host'; + /** * The table associated with the model. * @@ -50,7 +38,7 @@ class DatabaseHost extends Model * @var array */ protected $fillable = [ - 'name', 'host', 'port', 'username', 'max_databases', 'node_id', + 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', ]; /** @@ -65,24 +53,31 @@ class DatabaseHost extends Model ]; /** - * Sets the database connection name with the details of the host. + * Application validation rules. * - * @param string $connection - * @return void + * @var array */ - public function setDynamicConnection($connection = 'dynamic') - { - Config::set('database.connections.' . $connection, [ - 'driver' => 'mysql', - 'host' => $this->host, - 'port' => $this->port, - 'database' => 'mysql', - 'username' => $this->username, - 'password' => Crypt::decrypt($this->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - ]); - } + protected static $applicationRules = [ + 'name' => 'required', + 'host' => 'required', + 'port' => 'required', + 'username' => 'required', + 'node_id' => 'sometimes', + ]; + + /** + * Validation rules to assign to this model. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'string|max:255', + 'host' => 'ip|unique:database_hosts,host', + 'port' => 'numeric|between:1,65535', + 'username' => 'string|max:32', + 'password' => 'nullable|string', + 'node_id' => 'nullable|integer|exists:nodes,id', + ]; /** * Gets the node associated with a database host. diff --git a/app/Models/Egg.php b/app/Models/Egg.php new file mode 100644 index 000000000..c9b2d9767 --- /dev/null +++ b/app/Models/Egg.php @@ -0,0 +1,240 @@ + 'integer', + 'config_from' => 'integer', + 'script_is_privileged' => 'boolean', + 'copy_script_from' => 'integer', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'nest_id' => 'required', + 'uuid' => 'required', + 'name' => 'required', + 'description' => 'required', + 'author' => 'required', + 'docker_image' => 'required', + 'startup' => 'required', + 'config_from' => 'sometimes', + 'config_stop' => 'required_without:config_from', + 'config_startup' => 'required_without:config_from', + 'config_logs' => 'required_without:config_from', + 'config_files' => 'required_without:config_from', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'nest_id' => 'bail|numeric|exists:nests,id', + 'uuid' => 'string|size:36', + 'name' => 'string|max:255', + 'description' => 'string', + 'author' => 'string|email', + 'docker_image' => 'string|max:255', + 'startup' => 'nullable|string', + 'config_from' => 'bail|nullable|numeric|exists:eggs,id', + 'config_stop' => 'nullable|string|max:255', + 'config_startup' => 'nullable|json', + 'config_logs' => 'nullable|json', + 'config_files' => 'nullable|json', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'config_stop' => null, + 'config_startup' => null, + 'config_logs' => null, + 'config_files' => null, + ]; + + /** + * Returns the install script for the egg; if egg is copying from another + * it will return the copied script. + * + * @return string + */ + public function getCopyScriptInstallAttribute() + { + return (is_null($this->copy_script_from)) ? $this->script_install : $this->scriptFrom->script_install; + } + + /** + * 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() + { + return (is_null($this->copy_script_from)) ? $this->script_entry : $this->scriptFrom->script_entry; + } + + /** + * 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() + { + return (is_null($this->copy_script_from)) ? $this->script_container : $this->scriptFrom->script_container; + } + + /** + * Return the file configuration for an egg. + * + * @return string + */ + public function getInheritConfigFilesAttribute() + { + return is_null($this->config_from) ? $this->config_files : $this->configFrom->config_files; + } + + /** + * Return the startup configuration for an egg. + * + * @return string + */ + public function getInheritConfigStartupAttribute() + { + return is_null($this->config_from) ? $this->config_startup : $this->configFrom->config_startup; + } + + /** + * Return the log reading configuration for an egg. + * + * @return string + */ + public function getInheritConfigLogsAttribute() + { + return is_null($this->config_from) ? $this->config_logs : $this->configFrom->config_logs; + } + + /** + * Return the stop command configuration for an egg. + * + * @return string + */ + public function getInheritConfigStopAttribute() + { + return is_null($this->config_from) ? $this->config_stop : $this->configFrom->config_stop; + } + + /** + * Gets nest associated with an egg. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function nest() + { + return $this->belongsTo(Nest::class); + } + + /** + * Gets all servers associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class, 'egg_id'); + } + + /** + * Gets all variables associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function variables() + { + return $this->hasMany(EggVariable::class, 'egg_id'); + } + + /** + * Gets all packs associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function packs() + { + return $this->hasMany(Pack::class, 'egg_id'); + } + + /** + * Get the parent egg from which to copy scripts. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function scriptFrom() + { + 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() + { + return $this->belongsTo(self::class, 'config_from'); + } +} diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php new file mode 100644 index 000000000..293b86c4b --- /dev/null +++ b/app/Models/EggVariable.php @@ -0,0 +1,101 @@ + 'integer', + 'user_viewable' => 'integer', + 'user_editable' => 'integer', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'name' => 'required', + 'env_variable' => 'required', + 'rules' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'egg_id' => 'exists:eggs,id', + 'name' => 'string|between:1,255', + 'description' => 'string', + 'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES, + 'default_value' => 'string', + 'user_viewable' => 'boolean', + 'user_editable' => 'boolean', + 'rules' => 'string', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'user_editable' => 0, + 'user_viewable' => 0, + ]; + + /** + * @return bool + */ + public function getRequiredAttribute($value) + { + return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']); + } + + /** + * Return server variables associated with this variable. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function serverVariable() + { + return $this->hasMany(ServerVariable::class, 'variable_id'); + } +} diff --git a/app/Models/Location.php b/app/Models/Location.php index f9ceec767..c680a54da 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -1,33 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Location extends Model +class Location extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'location'; + /** * The table associated with the model. * @@ -42,6 +32,26 @@ class Location extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * Validation rules to apply to this model. + * + * @var array + */ + protected static $applicationRules = [ + 'short' => 'required', + 'long' => 'required', + ]; + + /** + * Rules ensuring that the raw data stored in the database meets expectations. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'short' => 'string|between:1,60|unique:locations,short', + 'long' => 'string|between:1,255', + ]; + /** * Gets the nodes in a specificed location. * diff --git a/app/Models/Nest.php b/app/Models/Nest.php new file mode 100644 index 000000000..a6ab112e6 --- /dev/null +++ b/app/Models/Nest.php @@ -0,0 +1,85 @@ + 'required', + 'name' => 'required', + 'description' => 'sometimes', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'author' => 'string|email', + 'name' => 'string|max:255', + 'description' => 'nullable|string', + ]; + + /** + * Gets all eggs associated with this service. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function eggs() + { + return $this->hasMany(Egg::class); + } + + /** + * Returns all of the packs associated with a nest, regardless of the egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function packs() + { + return $this->hasManyThrough(Pack::class, Egg::class, 'nest_id', 'egg_id'); + } + + /** + * Gets all servers associated with this nest. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class); + } +} diff --git a/app/Models/Node.php b/app/Models/Node.php index 9c454cfcb..d5d5ac9bd 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -1,37 +1,25 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; -use GuzzleHttp\Client; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Node extends Model +class Node extends Model implements CleansAttributes, ValidableContract { - use Notifiable, SearchableTrait; + use Eloquence, Notifiable, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'node'; + + const DAEMON_SECRET_LENGTH = 36; /** * The table associated with the model. @@ -47,20 +35,20 @@ class Node extends Model */ protected $hidden = ['daemonSecret']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'public' => 'integer', - 'location_id' => 'integer', - 'memory' => 'integer', - 'disk' => 'integer', - 'daemonListen' => 'integer', - 'daemonSFTP' => 'integer', - 'behind_proxy' => 'boolean', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'public' => 'integer', + 'location_id' => 'integer', + 'memory' => 'integer', + 'disk' => 'integer', + 'daemonListen' => 'integer', + 'daemonSFTP' => 'integer', + 'behind_proxy' => 'boolean', + ]; /** * Fields that are mass assignable. @@ -81,38 +69,67 @@ class Node extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'nodes.name' => 10, - 'nodes.fqdn' => 8, - 'locations.short' => 4, - 'locations.long' => 4, - ], - 'joins' => [ - 'locations' => ['locations.id', 'nodes.location_id'], - ], - ]; + protected $searchableColumns = [ + 'name' => 10, + 'fqdn' => 8, + 'location.short' => 4, + 'location.long' => 4, + ]; /** - * Return an instance of the Guzzle client for this specific node. - * - * @param array $headers - * @return \GuzzleHttp\Client + * @var array */ - public function guzzleClient($headers = []) - { - return new Client([ - 'base_uri' => sprintf('%s://%s:%s/', $this->scheme, $this->fqdn, $this->daemonListen), - 'timeout' => config('pterodactyl.guzzle.timeout'), - 'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'), - 'headers' => $headers, - ]); - } + protected static $applicationRules = [ + 'name' => 'required', + 'location_id' => 'required', + 'fqdn' => 'required', + 'scheme' => 'required', + 'memory' => 'required', + 'memory_overallocate' => 'required', + 'disk' => 'required', + 'disk_overallocate' => 'required', + 'daemonBase' => 'sometimes|required', + 'daemonSFTP' => 'required', + 'daemonListen' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'regex:/^([\w .-]{1,100})$/', + 'location_id' => 'exists:locations,id', + 'public' => 'boolean', + 'fqdn' => 'string', + 'behind_proxy' => 'boolean', + 'memory' => 'numeric|min:1', + 'memory_overallocate' => 'numeric|min:-1', + 'disk' => 'numeric|min:1', + 'disk_overallocate' => 'numeric|min:-1', + 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', + 'daemonSFTP' => 'numeric|between:1024,65535', + 'daemonListen' => 'numeric|between:1024,65535', + ]; + + /** + * Default values for specific columns that are generally not changed on base installs. + * + * @var array + */ + protected $attributes = [ + 'public' => true, + 'behind_proxy' => false, + 'memory_overallocate' => 0, + 'disk_overallocate' => 0, + 'daemonBase' => '/srv/daemon-data', + 'daemonSFTP' => 2022, + 'daemonListen' => 8080, + ]; /** * Returns the configuration in JSON format. * - * @param bool $pretty + * @param bool $pretty * @return string */ public function getConfigurationAsJson($pretty = false) @@ -128,13 +145,26 @@ class Node extends Model ], ], 'docker' => [ + 'container' => [ + 'user' => null, + ], + 'network' => [ + 'name' => 'pterodactyl_nw', + ], 'socket' => '/var/run/docker.sock', 'autoupdate_images' => true, ], + 'filesystem' => [ + 'server_logs' => '/tmp/pterodactyl', + ], 'sftp' => [ 'path' => $this->daemonBase, + 'ip' => '0.0.0.0', 'port' => $this->daemonSFTP, - 'container' => 'ptdl-sftp', + 'keypair' => [ + 'bits' => 2048, + 'e' => 65537, + ], ], 'logger' => [ 'path' => 'logs/', diff --git a/app/Models/Objects/DeploymentObject.php b/app/Models/Objects/DeploymentObject.php new file mode 100644 index 000000000..52857410f --- /dev/null +++ b/app/Models/Objects/DeploymentObject.php @@ -0,0 +1,78 @@ +dedicated; + } + + /** + * @param bool $dedicated + * @return $this + */ + public function setDedicated(bool $dedicated) + { + $this->dedicated = $dedicated; + + return $this; + } + + /** + * @return array + */ + public function getLocations(): array + { + return $this->locations; + } + + /** + * @param array $locations + * @return $this + */ + public function setLocations(array $locations) + { + $this->locations = $locations; + + return $this; + } + + /** + * @return array + */ + public function getPorts(): array + { + return $this->ports; + } + + /** + * @param array $ports + * @return $this + */ + public function setPorts(array $ports) + { + $this->ports = $ports; + + return $this; + } +} diff --git a/app/Models/Pack.php b/app/Models/Pack.php index 9cda19790..657d2f1d0 100644 --- a/app/Models/Pack.php +++ b/app/Models/Pack.php @@ -1,37 +1,22 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; -use File; -use Storage; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Pack extends Model +class Pack extends Model implements CleansAttributes, ValidableContract { - use SearchableTrait; + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'pack'; /** * The table associated with the model. @@ -46,7 +31,33 @@ class Pack extends Model * @var array */ protected $fillable = [ - 'option_id', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + 'egg_id', 'uuid', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'name' => 'required', + 'version' => 'required', + 'description' => 'sometimes', + 'selectable' => 'sometimes|required', + 'visible' => 'sometimes|required', + 'locked' => 'sometimes|required', + 'egg_id' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'string', + 'version' => 'string', + 'description' => 'nullable|string', + 'selectable' => 'boolean', + 'visible' => 'boolean', + 'locked' => 'boolean', + 'egg_id' => 'exists:eggs,id', ]; /** @@ -55,7 +66,7 @@ class Pack extends Model * @var array */ protected $casts = [ - 'option_id' => 'integer', + 'egg_id' => 'integer', 'selectable' => 'boolean', 'visible' => 'boolean', 'locked' => 'boolean', @@ -66,51 +77,22 @@ class Pack extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'packs.name' => 10, - 'packs.uuid' => 8, - 'service_options.name' => 6, - 'service_options.tag' => 5, - 'service_options.docker_image' => 5, - 'packs.version' => 2, - ], - 'joins' => [ - 'service_options' => ['packs.option_id', 'service_options.id'], - ], + protected $searchableColumns = [ + 'name' => 10, + 'uuid' => 8, + 'egg.name' => 6, + 'egg.docker_image' => 5, + 'version' => 2, ]; /** - * Returns all of the archived files for a given pack. - * - * @param bool $collection - * @return \Illuminate\Support\Collection|object - */ - public function files($collection = false) - { - $files = collect(Storage::files('packs/' . $this->uuid)); - - $files = $files->map(function ($item) { - $path = storage_path('app/' . $item); - - return (object) [ - 'name' => basename($item), - 'hash' => sha1_file($path), - 'size' => File::humanReadableSize($path), - ]; - }); - - return ($collection) ? $files : (object) $files->all(); - } - - /** - * Gets option associated with a service pack. + * Gets egg associated with a service pack. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function option() + public function egg() { - return $this->belongsTo(ServiceOption::class); + return $this->belongsTo(Egg::class); } /** diff --git a/app/Models/Permission.php b/app/Models/Permission.php index c126967ec..c9fd886fe 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -1,33 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Permission extends Model +class Permission extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'subuser_permission'; + /** * Should timestamps be used on this model. * @@ -58,6 +48,22 @@ class Permission extends Model 'subuser_id' => 'integer', ]; + /** + * @var array + */ + protected static $applicationRules = [ + 'subuser_id' => 'required', + 'permission' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'subuser_id' => 'numeric|min:1', + 'permission' => 'string', + ]; + /** * A list of all permissions available for a user. * @@ -79,16 +85,17 @@ class Permission extends Model 'delete-subuser' => null, ], 'server' => [ - 'set-connection' => null, + 'view-allocations' => null, + 'edit-allocation' => null, 'view-startup' => null, - 'edit-startup' => null, + 'edit-startup' => null, ], - 'sftp' => [ - 'view-sftp' => null, - 'view-sftp-password' => null, - 'reset-sftp' => 's:set-password', + 'database' => [ + 'view-databases' => null, + 'reset-db-password' => null, ], 'file' => [ + 'access-sftp' => null, 'list-files' => 's:files:get', 'edit-files' => 's:files:read', 'save-files' => 's:files:post', @@ -99,31 +106,28 @@ class Permission extends Model 'create-files' => 's:files:create', 'upload-files' => 's:files:upload', 'delete-files' => 's:files:delete', - 'download-files' => null, + 'download-files' => 's:files:download', ], 'task' => [ - 'list-tasks' => null, - 'view-task' => null, - 'toggle-task' => null, - 'queue-task' => null, - 'create-task' => null, - 'delete-task' => null, - ], - 'database' => [ - 'view-databases' => null, - 'reset-db-password' => null, + 'list-schedules' => null, + 'view-schedule' => null, + 'toggle-schedule' => null, + 'queue-schedule' => null, + 'edit-schedule' => null, + 'create-schedule' => null, + 'delete-schedule' => null, ], ]; /** * Return a collection of permissions available. * - * @param array $single - * @return \Illuminate\Support\Collection|array + * @param bool $array + * @return array|\Illuminate\Support\Collection */ - public static function listPermissions($single = false) + public static function getPermissions($array = false) { - if ($single) { + if ($array) { return collect(self::$permissions)->mapWithKeys(function ($item) { return $item; })->all(); @@ -135,8 +139,8 @@ class Permission extends Model /** * Find permission by permission node. * - * @param \Illuminate\Database\Query\Builder $query - * @param string $permission + * @param \Illuminate\Database\Query\Builder $query + * @param string $permission * @return \Illuminate\Database\Query\Builder */ public function scopePermission($query, $permission) @@ -147,8 +151,8 @@ class Permission extends Model /** * Filter permission by server. * - * @param \Illuminate\Database\Query\Builder $query - * @param \Pterodactyl\Models\Server $server + * @param \Illuminate\Database\Query\Builder $query + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Database\Query\Builder */ public function scopeServer($query, Server $server) diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php new file mode 100644 index 000000000..83971d797 --- /dev/null +++ b/app/Models/Schedule.php @@ -0,0 +1,137 @@ + 'integer', + 'server_id' => 'integer', + 'is_active' => 'boolean', + 'is_processing' => 'boolean', + ]; + + /** + * Columns to mutate to a date. + * + * @var array + */ + protected $dates = [ + self::CREATED_AT, + self::UPDATED_AT, + 'last_run_at', + 'next_run_at', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'name' => null, + 'cron_day_of_week' => '*', + 'cron_day_of_month' => '*', + 'cron_hour' => '*', + 'cron_minute' => '*', + 'is_active' => true, + 'is_processing' => false, + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'server_id' => 'required', + 'cron_day_of_week' => 'required', + 'cron_day_of_month' => 'required', + 'cron_hour' => 'required', + 'cron_minute' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'server_id' => 'exists:servers,id', + 'name' => 'nullable|string|max:255', + 'cron_day_of_week' => 'string', + 'cron_day_of_month' => 'string', + 'cron_hour' => 'string', + 'cron_minute' => 'string', + 'is_active' => 'boolean', + 'is_processing' => 'boolean', + 'last_run_at' => 'nullable|date', + 'next_run_at' => 'nullable|date', + ]; + + /** + * Return a hashid encoded string to represent the ID of the schedule. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + + /** + * Return tasks belonging to a schedule. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tasks() + { + return $this->hasMany(Task::class); + } + + /** + * Return the server model that a schedule belongs to. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index 1a26243e9..f10e52b1d 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1,41 +1,25 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; -use Auth; -use Cache; -use Carbon; use Schema; -use Javascript; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Nicolaslopezj\Searchable\SearchableTrait; +use Znck\Eloquent\Traits\BelongsToThrough; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Server extends Model +class Server extends Model implements CleansAttributes, ValidableContract { - use Notifiable, SearchableTrait; + use BelongsToThrough, Eloquence, Notifiable, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'server'; /** * The table associated with the model. @@ -44,26 +28,70 @@ class Server extends Model */ protected $table = 'servers'; - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = ['daemonSecret', 'sftp_password']; - /** * The attributes that should be mutated to dates. * * @var array */ - protected $dates = ['deleted_at']; + protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; + + /** + * Always eager load these relationships on the model. + * + * @var array + */ + protected $with = ['key']; /** * Fields that are not mass assignable. * * @var array */ - protected $guarded = ['id', 'installed', 'created_at', 'updated_at', 'deleted_at']; + protected $guarded = ['id', 'installed', self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; + + /** + * @var array + */ + protected static $applicationRules = [ + 'owner_id' => 'required', + 'name' => 'required', + 'memory' => 'required', + 'swap' => 'required', + 'io' => 'required', + 'cpu' => 'required', + 'disk' => 'required', + 'nest_id' => 'required', + 'egg_id' => 'required', + 'node_id' => 'required', + 'allocation_id' => 'required', + 'pack_id' => 'sometimes', + 'skip_scripts' => 'sometimes', + 'image' => 'required', + 'startup' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'owner_id' => 'integer|exists:users,id', + 'name' => 'string|min:1|max:255', + 'node_id' => 'exists:nodes,id', + 'description' => 'string', + 'memory' => 'numeric|min:0', + 'swap' => 'numeric|min:-1', + 'io' => 'numeric|between:10,1000', + 'cpu' => 'numeric|min:0', + 'disk' => 'numeric|min:0', + 'allocation_id' => 'bail|unique:servers|exists:allocations,id', + 'nest_id' => 'exists:nests,id', + 'egg_id' => 'exists:eggs,id', + 'pack_id' => 'nullable|numeric|min:0', + 'startup' => 'string', + 'skip_scripts' => 'boolean', + 'image' => 'string|max:255', + 'installed' => 'boolean', + ]; /** * Cast values to correct type. @@ -82,8 +110,8 @@ class Server extends Model 'cpu' => 'integer', 'oom_disabled' => 'integer', 'allocation_id' => 'integer', - 'service_id' => 'integer', - 'option_id' => 'integer', + 'nest_id' => 'integer', + 'egg_id' => 'integer', 'pack_id' => 'integer', 'installed' => 'integer', ]; @@ -93,126 +121,16 @@ class Server extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'servers.name' => 10, - 'servers.username' => 10, - 'servers.uuidShort' => 9, - 'servers.uuid' => 8, - 'packs.name' => 7, - 'users.email' => 6, - 'users.username' => 6, - 'nodes.name' => 2, - ], - 'joins' => [ - 'packs' => ['packs.id', 'servers.pack_id'], - 'users' => ['users.id', 'servers.owner_id'], - 'nodes' => ['nodes.id', 'servers.node_id'], - ], + protected $searchableColumns = [ + 'name' => 50, + 'uuidShort' => 10, + 'uuid' => 10, + 'pack.name' => 5, + 'user.email' => 20, + 'user.username' => 20, + 'node.name' => 10, ]; - /** - * Returns a single server specified by UUID. - * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. - * YOU WILL OVERWRITE THE SECRET KEY AND BREAK THINGS. - * - * @param string $uuid - * @param array $with - * @param array $withCount - * @return \Pterodactyl\Models\Server - * @todo Remove $with and $withCount due to cache issues, they aren't used anyways. - */ - public static function byUuid($uuid, array $with = [], array $withCount = []) - { - if (! Auth::check()) { - throw new \Exception('You must call Server:byUuid as an authenticated user.'); - } - - // Results are cached because we call this functions a few times on page load. - $result = Cache::tags(['Model:Server', 'Model:Server:byUuid:' . $uuid])->remember('Model:Server:byUuid:' . $uuid . Auth::user()->uuid, Carbon::now()->addMinutes(15), function () use ($uuid) { - $query = self::with('service', 'node')->where(function ($q) use ($uuid) { - $q->where('uuidShort', $uuid)->orWhere('uuid', $uuid); - }); - - if (! Auth::user()->isRootAdmin()) { - $query->whereIn('id', Auth::user()->serverAccessArray()); - } - - return $query->first(); - }); - - if (! is_null($result)) { - $result->daemonSecret = Auth::user()->daemonToken($result); - } - - return $result; - } - - /** - * Returns non-administrative headers for accessing a server on the daemon. - * - * @param Pterodactyl\Models\User|null $user - * @return array - */ - public function guzzleHeaders(User $user = null) - { - // If no specific user is passed, see if we can find an active - // auth session to pull data from. - if (is_null($user) && Auth::check()) { - $user = Auth::user(); - } - - return [ - 'X-Access-Server' => $this->uuid, - 'X-Access-Token' => ($user) ? $user->daemonToken($this) : $this->daemonSecret, - ]; - } - - /** - * Return an instance of the Guzzle client for this specific server using defined access token. - * - * @param Pterodactyl\Models\User|null $user - * @return \GuzzleHttp\Client - */ - public function guzzleClient(User $user = null) - { - return $this->node->guzzleClient($this->guzzleHeaders($user)); - } - - /** - * Returns javascript object to be embedded on server view pages with relevant information. - * - * @param array|null $additional - * @param array|null $overwrite - * @return \Laracasts\Utilities\JavaScript\JavaScriptFacade - */ - public function js($additional = null, $overwrite = null) - { - $response = [ - 'server' => collect($this->makeVisible('daemonSecret'))->only([ - 'uuid', - 'uuidShort', - 'daemonSecret', - 'username', - ]), - 'node' => collect($this->node)->only([ - 'fqdn', - 'scheme', - 'daemonListen', - ]), - ]; - - if (is_array($additional)) { - $response = array_merge($response, $additional); - } - - if (is_array($overwrite)) { - $response = $overwrite; - } - - return Javascript::put($response); - } - /** * Return the columns available for this table. * @@ -274,23 +192,23 @@ class Server extends Model } /** - * Gets information for the service associated with this server. + * Gets information for the nest associated with this server. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function service() + public function nest() { - return $this->belongsTo(Service::class); + return $this->belongsTo(Nest::class); } /** - * Gets information for the service option associated with this server. + * Gets information for the egg associated with this server. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function option() + public function egg() { - return $this->belongsTo(ServiceOption::class); + return $this->belongsTo(Egg::class); } /** @@ -316,12 +234,11 @@ class Server extends Model /** * Gets information for the tasks associated with this server. * - * @TODO adjust server column in tasks to be server_id * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function tasks() + public function schedule() { - return $this->hasMany(Task::class); + return $this->hasMany(Schedule::class); } /** @@ -335,12 +252,34 @@ class Server extends Model } /** - * Gets the location of the server. + * Returns the location that a server belongs to. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Znck\Eloquent\Relations\BelongsToThrough + * + * @throws \Exception */ public function location() { - return $this->node->location(); + return $this->belongsToThrough(Location::class, Node::class); + } + + /** + * Return the key belonging to the server owner. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function key() + { + return $this->hasOne(DaemonKey::class, 'user_id', 'owner_id'); + } + + /** + * Returns all of the daemon keys belonging to this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function keys() + { + return $this->hasMany(DaemonKey::class); } } diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 165d5b3df..f706d5942 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -1,26 +1,4 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; @@ -28,6 +6,12 @@ use Illuminate\Database\Eloquent\Model; class ServerVariable extends Model { + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'server_variable'; + /** * The table associated with the model. * @@ -42,53 +26,53 @@ class ServerVariable extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ 'server_id' => 'integer', 'variable_id' => 'integer', ]; - /** - * Determine if variable is viewable by users. - * - * @return bool - */ - public function getUserCanViewAttribute() - { - return (bool) $this->variable->user_viewable; - } + /** + * Determine if variable is viewable by users. + * + * @return bool + */ + public function getUserCanViewAttribute() + { + return (bool) $this->variable->user_viewable; + } - /** - * Determine if variable is editable by users. - * - * @return bool - */ - public function getUserCanEditAttribute() - { - return (bool) $this->variable->user_editable; - } + /** + * Determine if variable is editable by users. + * + * @return bool + */ + public function getUserCanEditAttribute() + { + return (bool) $this->variable->user_editable; + } - /** - * Determine if variable is required. - * - * @return bool - */ - public function getRequiredAttribute() - { - return $this->variable->required; - } + /** + * Determine if variable is required. + * + * @return bool + */ + public function getRequiredAttribute() + { + return $this->variable->required; + } - /** - * Returns information about a given variables parent. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function variable() - { - return $this->belongsTo(ServiceVariable::class, 'variable_id'); - } + /** + * Returns information about a given variables parent. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function variable() + { + return $this->belongsTo(EggVariable::class, 'variable_id'); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php deleted file mode 100644 index b05366882..000000000 --- a/app/Models/Service.php +++ /dev/null @@ -1,122 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class Service extends Model -{ - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'services'; - - /** - * Fields that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'name', 'description', 'folder', 'startup', 'index_file', - ]; - - /** - * Returns the default contents of the index.js file for a service. - * - * @return string - */ - public static function defaultIndexFile() - { - return <<<'EOF' -'use strict'; - -/** - * Pterodactyl - Daemon - * Copyright (c) 2015 - 2017 Dane Everitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -const rfr = require('rfr'); -const _ = require('lodash'); - -const Core = rfr('src/services/index.js'); - -class Service extends Core {} - -module.exports = Service; -EOF; - } - - /** - * Gets all service options associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function options() - { - return $this->hasMany(ServiceOption::class); - } - - /** - * Returns all of the packs associated with a service, regardless of the service option. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function packs() - { - return $this->hasManyThrough( - 'Pterodactyl\Models\Pack', 'Pterodactyl\Models\ServiceOption', - 'service_id', 'option_id' - ); - } - - /** - * Gets all servers associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function servers() - { - return $this->hasMany(Server::class); - } -} diff --git a/app/Models/ServiceOption.php b/app/Models/ServiceOption.php deleted file mode 100644 index 5f0cd9c9e..000000000 --- a/app/Models/ServiceOption.php +++ /dev/null @@ -1,143 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class ServiceOption extends Model -{ - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'service_options'; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id', 'created_at', 'updated_at']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'service_id' => 'integer', - 'script_is_privileged' => 'boolean', - ]; - - /** - * Returns the display startup string for the option and will use the parent - * service one if the option does not have one defined. - * - * @return string - */ - public function getDisplayStartupAttribute($value) - { - return (is_null($this->startup)) ? $this->service->startup : $this->startup; - } - - /** - * Returns the install script for the option; if option is copying from another - * it will return the copied script. - * - * @return string - */ - public function getCopyScriptInstallAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_install : $this->copyFrom->script_install; - } - - /** - * Returns the entry command for the option; if option is copying from another - * it will return the copied entry command. - * - * @return string - */ - public function getCopyScriptEntryAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_entry : $this->copyFrom->script_entry; - } - - /** - * Returns the install container for the option; if option is copying from another - * it will return the copied install container. - * - * @return string - */ - public function getCopyScriptContainerAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_container : $this->copyFrom->script_container; - } - - /** - * Gets service associated with a service option. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function service() - { - return $this->belongsTo(Service::class); - } - - /** - * Gets all servers associated with this service option. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function servers() - { - return $this->hasMany(Server::class, 'option_id'); - } - - /** - * Gets all variables associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function variables() - { - return $this->hasMany(ServiceVariable::class, 'option_id'); - } - - /** - * Gets all packs associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function packs() - { - return $this->hasMany(Pack::class, 'option_id'); - } - - public function copyFrom() - { - return $this->belongsTo(self::class, 'copy_script_from'); - } -} diff --git a/app/Models/ServiceVariable.php b/app/Models/ServiceVariable.php deleted file mode 100644 index 93e93e7e9..000000000 --- a/app/Models/ServiceVariable.php +++ /dev/null @@ -1,100 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Models; - -use Illuminate\Database\Eloquent\Model; - -class ServiceVariable extends Model -{ - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'service_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 = [ - 'option_id' => 'integer', - 'user_viewable' => 'integer', - 'user_editable' => 'integer', - ]; - - /** - * Reserved environment variable names. - * - * @var array - */ - protected static $reservedNames = [ - 'SERVER_MEMORY', - 'SERVER_IP', - 'SERVER_PORT', - 'ENV', - 'HOME', - 'USER', - ]; - - /** - * Returns an array of environment variable names that cannot be used. - * - * @return array - */ - public static function reservedNames() - { - return self::$reservedNames; - } - - /** - * Returns the display executable for the option and will use the parent - * service one if the option does not have one defined. - * - * @return bool - */ - public function getRequiredAttribute($value) - { - return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']); - } - - /** - * Return server variables associated with this variable. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function serverVariable() - { - return $this->hasMany(ServerVariable::class, 'variable_id'); - } -} diff --git a/app/Models/Session.php b/app/Models/Session.php index 4794d8e04..3eb9d526e 100644 --- a/app/Models/Session.php +++ b/app/Models/Session.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Models; diff --git a/app/Models/Setting.php b/app/Models/Setting.php new file mode 100644 index 000000000..90d41f3d4 --- /dev/null +++ b/app/Models/Setting.php @@ -0,0 +1,39 @@ + 'required|string|between:1,255', + 'value' => 'string', + ]; +} diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index a2eff9c9a..93c62217a 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -1,35 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Subuser extends Model +class Subuser extends Model implements CleansAttributes, ValidableContract { - use Notifiable; + use Eloquence, Notifiable, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'server_subuser'; /** * The table associated with the model. @@ -38,13 +26,6 @@ class Subuser extends Model */ protected $table = 'subusers'; - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = ['daemonSecret']; - /** * Fields that are not mass assignable. * @@ -62,6 +43,32 @@ class Subuser extends Model 'server_id' => 'integer', ]; + /** + * @var array + */ + protected static $applicationRules = [ + 'user_id' => 'required', + 'server_id' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'user_id' => 'numeric|exists:users,id', + 'server_id' => 'numeric|exists:servers,id', + ]; + + /** + * Return a hashid encoded string to represent the ID of the subuser. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + /** * Gets the server associated with a subuser. * @@ -91,4 +98,14 @@ class Subuser extends Model { return $this->hasMany(Permission::class); } + + /** + * Return the key that belongs to this subuser for the server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function key() + { + return $this->hasOne(DaemonKey::class, 'server_id', 'server_id')->where('daemon_keys.user_id', '=', $this->user_id); + } } diff --git a/app/Models/Task.php b/app/Models/Task.php index 5e44a8264..28c8e3237 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -1,33 +1,24 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Znck\Eloquent\Traits\BelongsToThrough; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Task extends Model +class Task extends Model implements CleansAttributes, ValidableContract { + use BelongsToThrough, Eloquence, Validable; + + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + const RESOURCE_NAME = 'schedule_task'; + /** * The table associated with the model. * @@ -36,11 +27,25 @@ class Task extends Model protected $table = 'tasks'; /** - * Fields that are not mass assignable. + * Relationships to be updated when this model is updated. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $touches = ['schedule']; + + /** + * Fields that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'schedule_id', + 'sequence_id', + 'action', + 'payload', + 'time_offset', + 'is_queued', + ]; /** * Cast values to correct type. @@ -49,36 +54,72 @@ class Task extends Model */ protected $casts = [ 'id' => 'integer', - 'user_id' => 'integer', - 'server_id' => 'integer', - 'queued' => 'boolean', - 'active' => 'boolean', + 'schedule_id' => 'integer', + 'sequence_id' => 'integer', + 'time_offset' => 'integer', + 'is_queued' => 'boolean', ]; /** - * The attributes that should be mutated to dates. + * Default attributes when creating a new model. * * @var array */ - protected $dates = ['last_run', 'next_run', 'created_at', 'updated_at']; + protected $attributes = [ + 'is_queued' => false, + ]; /** - * Gets the server associated with a task. + * @var array + */ + protected static $applicationRules = [ + 'schedule_id' => 'required', + 'sequence_id' => 'required', + 'action' => 'required', + 'payload' => 'required', + 'time_offset' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'schedule_id' => 'numeric|exists:schedules,id', + 'sequence_id' => 'numeric|min:1', + 'action' => 'string', + 'payload' => 'string', + 'time_offset' => 'numeric|between:0,900', + 'is_queued' => 'boolean', + ]; + + /** + * Return a hashid encoded string to represent the ID of the task. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + + /** + * Return the schedule that a task belongs to. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ + public function schedule() + { + 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() { - return $this->belongsTo(Server::class); - } - - /** - * Gets the user associated with a task. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() - { - return $this->belongsTo(User::class); + return $this->belongsToThrough(Server::class, Schedule::class); } } diff --git a/app/Models/TaskLog.php b/app/Models/TaskLog.php index 578c625c5..ec85677e1 100644 --- a/app/Models/TaskLog.php +++ b/app/Models/TaskLog.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Models; diff --git a/app/Models/User.php b/app/Models/User.php index 12504b6b0..88960994f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1,60 +1,48 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Models; -use Hash; -use Google2FA; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; +use Pterodactyl\Rules\Username; +use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Pterodactyl\Exceptions\DisplayException; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; use Illuminate\Auth\Passwords\CanResetPassword; +use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Foundation\Auth\Access\Authorizable; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract +class User extends Model implements + AuthenticatableContract, + AuthorizableContract, + CanResetPasswordContract, + CleansAttributes, + ValidableContract { - use Authenticatable, Authorizable, CanResetPassword, Notifiable, SearchableTrait; + use Authenticatable, Authorizable, AvailableLanguages, CanResetPassword, Eloquence, Notifiable, Validable { + gatherRules as eloquenceGatherRules; + } + + const USER_LEVEL_USER = 0; + const USER_LEVEL_ADMIN = 1; + + const FILTER_LEVEL_ALL = 0; + const FILTER_LEVEL_OWNER = 1; + const FILTER_LEVEL_ADMIN = 2; + const FILTER_LEVEL_SUBUSER = 3; /** - * The rules for user passwords. - * - * @var string + * The resource name for this model when it is transformed into an + * API representation using fractal. */ - const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; - - /** - * The regex rules for usernames. - * - * @var string - */ - const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + const RESOURCE_NAME = 'user'; /** * Level of servers to display when using access() on a user. @@ -75,7 +63,19 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * * @var array */ - protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar', 'root_admin']; + protected $fillable = [ + 'username', + 'email', + 'name_first', + 'name_last', + 'password', + 'language', + 'use_totp', + 'totp_secret', + 'totp_authenticated_at', + 'gravatar', + 'root_admin', + ]; /** * Cast values to correct type. @@ -83,78 +83,102 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * @var array */ protected $casts = [ - 'root_admin' => 'integer', - 'use_totp' => 'integer', - 'gravatar' => 'integer', + 'root_admin' => 'boolean', + 'use_totp' => 'boolean', + 'gravatar' => 'boolean', ]; + /** + * @var array + */ + protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at']; + /** * The attributes excluded from the model's JSON form. * * @var array */ - protected $hidden = ['password', 'remember_token', 'totp_secret']; + protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at']; /** * Parameters for search querying. * * @var array */ - protected $searchable = [ - 'columns' => [ - 'email' => 10, - 'username' => 9, - 'name_first' => 6, - 'name_last' => 6, - 'uuid' => 1, - ], + protected $searchableColumns = [ + 'username' => 100, + 'email' => 100, + 'external_id' => 80, + 'uuid' => 80, + 'name_first' => 40, + 'name_last' => 40, ]; - protected $query; + /** + * Default values for specific fields in the database. + * + * @var array + */ + protected $attributes = [ + 'root_admin' => false, + 'language' => 'en', + 'use_totp' => false, + 'totp_secret' => null, + ]; /** - * Enables or disables TOTP on an account if the token is valid. + * Rules verifying that the data passed in forms is valid and meets application logic rules. * - * @param int $token - * @return bool + * @var array */ - public function toggleTotp($token) - { - if (! Google2FA::verifyKey($this->totp_secret, $token, 1)) { - return false; - } - - $this->use_totp = ! $this->use_totp; - - return $this->save(); - } + protected static $applicationRules = [ + 'uuid' => 'required', + 'email' => 'required', + 'external_id' => 'sometimes', + 'username' => 'required', + 'name_first' => 'required', + 'name_last' => 'required', + 'password' => 'sometimes', + 'language' => 'sometimes', + 'use_totp' => 'sometimes', + ]; /** - * Set a user password to a new value assuming it meets the following requirements: - * - 8 or more characters in length - * - at least one uppercase character - * - at least one lowercase character - * - at least one number. + * Rules verifying that the data being stored matches the expectations of the database. * - * @param string $password - * @param string $regex - * @return void + * @var array */ - public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') - { - if (! preg_match($regex, $password)) { - throw new DisplayException('The password passed did not meet the minimum password requirements.'); - } + protected static $dataIntegrityRules = [ + 'uuid' => 'string|size:36|unique:users,uuid', + 'email' => 'email|unique:users,email', + 'external_id' => 'nullable|string|max:255|unique:users,external_id', + 'username' => 'between:1,255|unique:users,username', + 'name_first' => 'string|between:1,255', + 'name_last' => 'string|between:1,255', + 'password' => 'nullable|string', + 'root_admin' => 'boolean', + 'language' => 'string', + 'use_totp' => 'boolean', + 'totp_secret' => 'nullable|string', + ]; - $this->password = Hash::make($password); - $this->save(); + /** + * Implement language verification by overriding Eloquence's gather + * rules function. + */ + protected static function gatherRules() + { + $rules = self::eloquenceGatherRules(); + $rules['language'][] = new In(array_keys((new self)->getAvailableLanguages())); + $rules['username'][] = new Username; + + return $rules; } /** * Send the password reset notification. * - * @param string $token - * @return void + * @param string $token */ public function sendPasswordResetNotification($token) { @@ -162,98 +186,23 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac } /** - * Return true or false depending on wether the user is root admin or not. + * Store the username as a lowecase string. * - * @return bool + * @param string $value */ - public function isRootAdmin() + public function setUsernameAttribute(string $value) { - return $this->root_admin === 1; + $this->attributes['username'] = mb_strtolower($value); } /** - * Returns the user's daemon secret for a given server. + * Return a concated result for the accounts full name. * - * @param \Pterodactyl\Models\Server $server - * @return null|string + * @return string */ - public function daemonToken(Server $server) + public function getNameAttribute() { - if ($this->id === $server->owner_id || $this->isRootAdmin()) { - return $server->daemonSecret; - } - - $subuser = $this->subuserOf->where('server_id', $server->id)->first(); - - return ($subuser) ? $subuser->daemonSecret : null; - } - - /** - * Returns an array of all servers a user is able to access. - * Note: does not account for user admin status. - * - * @return array - */ - public function serverAccessArray() - { - return Server::select('id')->where('owner_id', $this->id)->union( - Subuser::select('server_id')->where('user_id', $this->id) - )->pluck('id')->all(); - } - - /** - * Change the access level for a given call to `access()` on the user. - * - * @param string $level can be all, admin, subuser, owner - * @return void - */ - public function setAccessLevel($level = 'all') - { - if (! in_array($level, ['all', 'admin', 'subuser', 'owner'])) { - $level = 'all'; - } - $this->accessLevel = $level; - - return $this; - } - - /** - * Returns an array of all servers a user is able to access. - * Note: does not account for user admin status. - * - * @param array $load - * @return \Illuiminate\Database\Eloquent\Builder - */ - public function access(...$load) - { - if (count($load) > 0 && is_null($load[0])) { - $query = Server::query(); - } else { - $query = Server::with(! empty($load) ? $load : ['service', 'node', 'allocation']); - } - - // If access level is set to owner, only display servers - // that the user owns. - if ($this->accessLevel === 'owner') { - $query->where('owner_id', $this->id); - } - - // If set to all, display all servers they can access, including - // those they access as an admin. - // - // If set to subuser, only return the servers they can access because - // they are owner, or marked as a subuser of the server. - if (($this->accessLevel === 'all' && ! $this->isRootAdmin()) || $this->accessLevel === 'subuser') { - $query->whereIn('id', $this->serverAccessArray()); - } - - // If set to admin, only display the servers a user can access - // as an administrator (leaves out owned and subuser of). - if ($this->accessLevel === 'admin' && $this->isRootAdmin()) { - $query->whereNotIn('id', $this->serverAccessArray()); - } - - return $query; + return $this->name_first . ' ' . $this->name_last; } /** @@ -285,4 +234,14 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac { return $this->hasMany(Subuser::class); } + + /** + * Return all of the daemon keys that a user belongs to. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function keys() + { + return $this->hasMany(DaemonKey::class); + } } diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index f92a7a477..8312c3bd1 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -1,29 +1,8 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Notifications; +use Pterodactyl\Models\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; @@ -34,7 +13,15 @@ class AccountCreated extends Notification implements ShouldQueue use Queueable; /** - * The password reset token to send. + * 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 object */ @@ -43,18 +30,19 @@ class AccountCreated extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param aray $user - * @return void + * @param \Pterodactyl\Models\User $user + * @param string|null $token */ - public function __construct(array $user) + public function __construct(User $user, string $token = null) { - $this->user = (object) $user; + $this->token = $token; + $this->user = $user; } /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -65,19 +53,19 @@ class AccountCreated extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { $message = (new MailMessage) ->greeting('Hello ' . $this->user->name . '!') - ->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.') + ->line('You are recieving this email because an account has been created for you on ' . config('app.name') . '.') ->line('Username: ' . $this->user->username) - ->line('Email: ' . $notifiable->email); + ->line('Email: ' . $this->user->email); - if (! is_null($this->user->token)) { - return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->user->token . '?email=' . $notifiable->email)); + if (! is_null($this->token)) { + return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . $this->user->email)); } return $message; diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php index 415b39fb0..2ecaa45f3 100644 --- a/app/Notifications/AddedToServer.php +++ b/app/Notifications/AddedToServer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Notifications; @@ -41,8 +26,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param array $server - * @return void + * @param array $server */ public function __construct(array $server) { @@ -52,7 +36,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -63,7 +47,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/RemovedFromServer.php b/app/Notifications/RemovedFromServer.php index d4831dbe4..d23aae54a 100644 --- a/app/Notifications/RemovedFromServer.php +++ b/app/Notifications/RemovedFromServer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Notifications; @@ -41,8 +26,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param array $server - * @return void + * @param array $server */ public function __construct(array $server) { @@ -52,7 +36,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -63,7 +47,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index 367f863ed..2f1519cec 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Notifications; @@ -43,8 +28,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param string $token - * @return void + * @param string $token */ public function __construct($token) { @@ -54,7 +38,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -65,7 +49,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/ServerCreated.php b/app/Notifications/ServerCreated.php deleted file mode 100644 index 9f881729a..000000000 --- a/app/Notifications/ServerCreated.php +++ /dev/null @@ -1,80 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Notifications; - -use Illuminate\Bus\Queueable; -use Illuminate\Notifications\Notification; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Notifications\Messages\MailMessage; - -class ServerCreated extends Notification implements ShouldQueue -{ - use Queueable; - - /** - * @var object - */ - public $server; - - /** - * Create a new notification instance. - * - * @param array $server - * @return void - */ - public function __construct(array $server) - { - $this->server = (object) $server; - } - - /** - * Get the notification's delivery channels. - * - * @param mixed $notifiable - * @return array - */ - public function via($notifiable) - { - return ['mail']; - } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail($notifiable) - { - return (new MailMessage) - ->line('A new server as been assigned to your account.') - ->line('Server Name: ' . $this->server->name) - ->line('Memory: ' . $this->server->memory . ' MB') - ->line('Node: ' . $this->server->node) - ->line('Type: ' . $this->server->service . ' - ' . $this->server->option) - ->action('Peel Off the Protective Wrap', route('server.index', $this->server->uuidShort)) - ->line('Please let us know if you have any additional questions or concerns!'); - } -} diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php index cd8c2187a..00db4b334 100644 --- a/app/Observers/ServerObserver.php +++ b/app/Observers/ServerObserver.php @@ -3,31 +3,14 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Observers; -use Cache; use Pterodactyl\Events; use Pterodactyl\Models\Server; -use Pterodactyl\Notifications\ServerCreated; use Illuminate\Foundation\Bus\DispatchesJobs; class ServerObserver @@ -37,8 +20,7 @@ class ServerObserver /** * Listen to the Server creating event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function creating(Server $server) { @@ -48,29 +30,17 @@ class ServerObserver /** * Listen to the Server created event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function created(Server $server) { event(new Events\Server\Created($server)); - - // Queue Notification Email - $server->user->notify((new ServerCreated([ - 'name' => $server->name, - 'memory' => $server->memory, - 'node' => $server->node->name, - 'service' => $server->service->name, - 'option' => $server->option->name, - 'uuidShort' => $server->uuidShort, - ]))); } /** * Listen to the Server deleting event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function deleting(Server $server) { @@ -80,8 +50,7 @@ class ServerObserver /** * Listen to the Server deleted event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function deleted(Server $server) { @@ -91,8 +60,7 @@ class ServerObserver /** * Listen to the Server saving event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function saving(Server $server) { @@ -102,8 +70,7 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function saved(Server $server) { @@ -113,8 +80,7 @@ class ServerObserver /** * Listen to the Server updating event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function updating(Server $server) { @@ -124,22 +90,10 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function updated(Server $server) { - /* - * The cached byUuid model calls are tagged with Model:Server:byUuid: - * so that they can be accessed regardless of if there is an Auth::user() - * defined or not. - * - * We can also delete all cached byUuid items using the Model:Server tag. - */ - Cache::tags('Model:Server:byUuid:' . $server->uuid)->flush(); - Cache::tags('Model:Server:byUuid:' . $server->uuidShort)->flush(); - Cache::tags('Downloads:Server:' . $server->uuid)->flush(); - event(new Events\Server\Updated($server)); } } diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index 57eab2680..009e65290 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Observers; @@ -34,8 +19,7 @@ class SubuserObserver /** * Listen to the Subuser creating event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function creating(Subuser $subuser) { @@ -45,8 +29,7 @@ class SubuserObserver /** * Listen to the Subuser created event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function created(Subuser $subuser) { @@ -62,8 +45,7 @@ class SubuserObserver /** * Listen to the Subuser deleting event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function deleting(Subuser $subuser) { @@ -73,8 +55,7 @@ class SubuserObserver /** * Listen to the Subuser deleted event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function deleted(Subuser $subuser) { diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index e7b07851d..dd29d908b 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -1,43 +1,18 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Observers; -use DB; -use Hash; -use Carbon; use Pterodactyl\Events; use Pterodactyl\Models\User; -use Pterodactyl\Notifications\AccountCreated; class UserObserver { + protected $uuid; + /** * Listen to the User creating event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function creating(User $user) { @@ -47,34 +22,17 @@ class UserObserver /** * Listen to the User created event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function created(User $user) { event(new Events\User\Created($user)); - - if ($user->password === 'unset') { - $token = hash_hmac('sha256', str_random(40), config('app.key')); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => Hash::make($token), - 'created_at' => Carbon::now()->toDateTimeString(), - ]); - } - - $user->notify(new AccountCreated([ - 'name' => $user->name_first, - 'username' => $user->username, - 'token' => (isset($token)) ? $token : null, - ])); } /** * Listen to the User deleting event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function deleting(User $user) { @@ -84,8 +42,7 @@ class UserObserver /** * Listen to the User deleted event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function deleted(User $user) { diff --git a/app/Policies/APIKeyPolicy.php b/app/Policies/APIKeyPolicy.php deleted file mode 100644 index 95846b9e4..000000000 --- a/app/Policies/APIKeyPolicy.php +++ /dev/null @@ -1,73 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Policies; - -use Cache; -use Carbon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\APIKey as Key; -use Pterodactyl\Models\APIPermission as Permission; - -class APIKeyPolicy -{ - /** - * Checks if the API key has permission to perform an action. - * - * @param \Pterodactyl\Models\User $user - * @param \Pterodactyl\Models\APIKey $key - * @param string $permission - * @return bool - */ - protected function checkPermission(User $user, Key $key, $permission) - { - // Non-administrative users cannot use administrative routes. - if (! starts_with($key, 'user.') && ! $user->isRootAdmin()) { - return false; - } - - // We don't tag this cache key with the user uuid because the key is already unique, - // and multiple users are not defiend for a single key. - $permissions = Cache::remember('APIKeyPolicy.' . $key->public, Carbon::now()->addSeconds(5), function () use ($key) { - return $key->permissions()->get()->transform(function ($item) { - return $item->permission; - })->values(); - }); - - return $permissions->search($permission, true) !== false; - } - - /** - * Determine if a user has permission to perform this action against the system. - * - * @param \Pterodactyl\Models\User $user - * @param string $permission - * @param \Pterodactyl\Models\APIKey $key - * @return bool - */ - public function before(User $user, $permission, Key $key) - { - return $this->checkPermission($user, $key, $permission); - } -} diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index 56cd359e1..9b4db6f05 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Policies; @@ -53,17 +38,30 @@ class ServerPolicy /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. * - * @param \Pterodactyl\Models\User $user - * @param string $ability - * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @param string $ability + * @param \Pterodactyl\Models\Server $server * @return bool */ public function before(User $user, $ability, Server $server) { - if ($user->isRootAdmin() || $server->owner_id === $user->id) { + if ($user->root_admin || $server->owner_id === $user->id) { return true; } return $this->checkPermission($user, $server, $ability); } + + /** + * 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) + { + // do nothing + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 03fafc49c..d9aee55e2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -1,65 +1,44 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Providers; use View; use Cache; -use Pterodactyl\Models; -use Pterodactyl\Observers; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Observers\UserObserver; +use Pterodactyl\Observers\ServerObserver; +use Pterodactyl\Observers\SubuserObserver; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. - * - * @return void */ public function boot() { - Models\User::observe(Observers\UserObserver::class); - Models\Server::observe(Observers\ServerObserver::class); - Models\Subuser::observe(Observers\SubuserObserver::class); + 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); } /** - * Register any application services. - * - * @return void + * Register application service providers. */ public function register() { - if ($this->app->environment() !== 'production') { - $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class); - } - - if (config('pterodactyl.auth.notifications')) { - $this->app->register(\DaneEveritt\LoginNotifications\NotificationServiceProvider::class); + // Only load the settings service provider if the environment + // is configured to allow it. + if (! config('pterodactyl.load_environment_only', false) && $this->app->environment() !== 'testing') { + $this->app->register(SettingsServiceProvider::class); } } @@ -73,7 +52,10 @@ class AppServiceProvider extends ServiceProvider return Cache::remember('git-version', 5, function () { if (file_exists(base_path('.git/HEAD'))) { $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); - $path = base_path('.git/' . trim($head[1])); + + if (array_key_exists(1, $head)) { + $path = base_path('.git/' . trim($head[1])); + } } if (isset($path) && file_exists($path)) { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e1401e844..947750ae8 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -13,14 +13,12 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', - 'Pterodactyl\Models\APIKey' => 'Pterodactyl\Policies\APIKeyPolicy', ]; /** * Register any application authentication / authorization services. * - * @param \Illuminate\Contracts\Auth\Access\Gate $gate - * @return void + * @param \Illuminate\Contracts\Auth\Access\Gate $gate */ public function boot() { diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php new file mode 100644 index 000000000..97b0df48e --- /dev/null +++ b/app/Providers/BladeServiceProvider.php @@ -0,0 +1,19 @@ +app->make('blade.compiler') + ->directive('datetimeHuman', function ($expression) { + return "setTimezone(config('app.timezone'))->toDateTimeString(); ?>"; + }); + } +} diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index e61e610d5..3f7c84be4 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -9,8 +9,6 @@ class BroadcastServiceProvider extends ServiceProvider { /** * Bootstrap any application services. - * - * @return void */ public function boot() { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 9126aa6ae..2ecc663fe 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Providers; -use Illuminate\Support\Facades\Event; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider @@ -13,14 +12,4 @@ class EventServiceProvider extends ServiceProvider * @var array */ protected $listen = []; - - /** - * Register any other events for your application. - * - * @return void - */ - public function boot() - { - parent::boot(); - } } diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php new file mode 100644 index 000000000..ae16d8756 --- /dev/null +++ b/app/Providers/HashidsServiceProvider.php @@ -0,0 +1,36 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Providers; + +use Pterodactyl\Extensions\Hashids; +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + +class HashidsServiceProvider extends ServiceProvider +{ + /** + * Register the ability to use Hashids. + */ + 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', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + ); + }); + + $this->app->alias(HashidsInterface::class, 'hashids'); + } +} diff --git a/app/Providers/MacroServiceProvider.php b/app/Providers/MacroServiceProvider.php index 8dd08f73b..ddfbf7aa8 100644 --- a/app/Providers/MacroServiceProvider.php +++ b/app/Providers/MacroServiceProvider.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Providers; @@ -28,15 +13,14 @@ use File; use Cache; use Carbon; use Request; -use Pterodactyl\Models\APIKey; +use Pterodactyl\Models\ApiKey; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Services\ApiKeyService; class MacroServiceProvider extends ServiceProvider { /** * Bootstrap the application services. - * - * @return void */ public function boot() { @@ -60,14 +44,14 @@ class MacroServiceProvider extends ServiceProvider $parts = explode('.', Request::bearerToken()); - if (count($parts) === 2 && strlen($parts[0]) === APIKey::PUBLIC_KEY_LEN) { + if (count($parts) === 2 && strlen($parts[0]) === ApiKeyService::PUB_CRYPTO_BYTES * 2) { // Because the key itself isn't changing frequently, we simply cache this for // 15 minutes to speed up the API and keep requests flowing. return Cache::tags([ 'ApiKeyMacro', 'ApiKeyMacro:Key:' . $parts[0], ])->remember('ApiKeyMacro.' . $parts[0], Carbon::now()->addMinutes(15), function () use ($parts) { - return APIKey::where('public', $parts[0])->first(); + return ApiKey::where('public', $parts[0])->first(); }); } diff --git a/app/Providers/PhraseAppTranslationProvider.php b/app/Providers/PhraseAppTranslationProvider.php index 840234917..1365bd021 100644 --- a/app/Providers/PhraseAppTranslationProvider.php +++ b/app/Providers/PhraseAppTranslationProvider.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Providers; @@ -32,8 +17,6 @@ class PhraseAppTranslationProvider extends TranslationServiceProvider { /** * Register the service provider. - * - * @return void */ public function register() { diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php new file mode 100644 index 000000000..aa5fbbaa5 --- /dev/null +++ b/app/Providers/RepositoryServiceProvider.php @@ -0,0 +1,93 @@ +app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); + $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); + $this->app->bind(DaemonKeyRepositoryInterface::class, DaemonKeyRepository::class); + $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); + $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); + $this->app->bind(EggRepositoryInterface::class, EggRepository::class); + $this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class); + $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); + $this->app->bind(NestRepositoryInterface::class, NestRepository::class); + $this->app->bind(NodeRepositoryInterface::class, NodeRepository::class); + $this->app->bind(PackRepositoryInterface::class, PackRepository::class); + $this->app->bind(PermissionRepositoryInterface::class, PermissionRepository::class); + $this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class); + $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); + $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); + $this->app->bind(SessionRepositoryInterface::class, SessionRepository::class); + $this->app->bind(SettingsRepositoryInterface::class, SettingsRepository::class); + $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); + $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); + $this->app->bind(UserRepositoryInterface::class, UserRepository::class); + + // Daemon Repositories + $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); + $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); + $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); + $this->app->bind(FileRepositoryInterface::class, FileRepository::class); + $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 00d40bdbd..f9f6ac31d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -16,31 +16,11 @@ class RouteServiceProvider extends ServiceProvider */ protected $namespace = 'Pterodactyl\Http\Controllers'; - /** - * Define your route model bindings, pattern filters, etc. - * - * @return void - */ - public function boot() - { - parent::boot(); - } - /** * Define the routes for the application. - * - * @return void */ public function map() { - Route::middleware(['api'])->prefix('/api/user') - ->namespace($this->namespace . '\API\User') - ->group(base_path('routes/api.php')); - - Route::middleware(['api'])->prefix('/api/admin') - ->namespace($this->namespace . '\API\Admin') - ->group(base_path('routes/api-admin.php')); - Route::middleware(['web', 'auth', 'csrf']) ->namespace($this->namespace . '\Base') ->group(base_path('routes/base.php')); @@ -53,11 +33,19 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace . '\Auth') ->group(base_path('routes/auth.php')); - Route::middleware(['web', 'auth', 'server', 'csrf'])->prefix('/server/{server}') + Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth'])->prefix('/server/{server}') ->namespace($this->namespace . '\Server') ->group(base_path('routes/server.php')); - Route::middleware(['web', 'daemon'])->prefix('/daemon') + Route::middleware(['api'])->prefix('/api/application') + ->namespace($this->namespace . '\Api\Application') + ->group(base_path('routes/api-application.php')); + + Route::middleware(['daemon'])->prefix('/api/remote') + ->namespace($this->namespace . '\Api\Remote') + ->group(base_path('routes/api-remote.php')); + + Route::middleware(['web', 'daemon-old'])->prefix('/daemon') ->namespace($this->namespace . '\Daemon') ->group(base_path('routes/daemon.php')); } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php new file mode 100644 index 000000000..1b9596d80 --- /dev/null +++ b/app/Providers/SettingsServiceProvider.php @@ -0,0 +1,124 @@ +get('mail.driver') === 'smtp') { + $this->keys = array_merge($this->keys, $this->emailKeys); + } + + try { + $values = $settings->all()->mapWithKeys(function ($setting) { + return [$setting->key => $setting->value]; + })->toArray(); + } catch (QueryException $exception) { + $log->notice('A query exception was encountered while trying to load settings from the database.'); + + return; + } + + foreach ($this->keys as $key) { + $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); + if (in_array($key, self::$encrypted)) { + try { + $value = $encrypter->decrypt($value); + } catch (DecryptException $exception) { + } + } + + switch (strtolower($value)) { + case 'true': + case '(true)': + $value = true; + break; + case 'false': + case '(false)': + $value = false; + break; + case 'empty': + case '(empty)': + $value = ''; + break; + case 'null': + case '(null)': + $value = null; + } + + $config->set(str_replace(':', '.', $key), $value); + } + } + + /** + * @return array + */ + public static function getEncryptedKeys(): array + { + return self::$encrypted; + } +} diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php new file mode 100644 index 000000000..dddc925f4 --- /dev/null +++ b/app/Providers/ViewComposerServiceProvider.php @@ -0,0 +1,24 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Providers; + +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer; + +class ViewComposerServiceProvider extends ServiceProvider +{ + /** + * Register bindings in the container. + */ + public function boot() + { + $this->app->make('view')->composer('server.*', ServerDataComposer::class); + } +} diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php deleted file mode 100644 index 10af25155..000000000 --- a/app/Repositories/APIRepository.php +++ /dev/null @@ -1,207 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Auth; -use Crypt; -use Validator; -use IPTools\Network; -use Pterodactyl\Models\User; -use Pterodactyl\Models\APIKey as Key; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Models\APIPermission as Permission; -use Pterodactyl\Exceptions\DisplayValidationException; - -class APIRepository -{ - /** - * Holder for listing of allowed IPs when creating a new key. - * - * @var array - */ - protected $allowed = []; - - /** - * The eloquent model for a user. - * - * @var \Pterodactyl\Models\User - */ - protected $user; - - /** - * Constructor for API Repository. - * - * @param null|\Pterodactyl\Models\User $user - * @return void - */ - public function __construct(User $user = null) - { - $this->user = is_null($user) ? Auth::user() : $user; - if (is_null($this->user)) { - throw new \Exception('Unable to initialize user for API repository instance.'); - } - } - - /** - * Create a New API Keypair on the system. - * - * @param array $data - * @return string - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'memo' => 'string|max:500', - 'allowed_ips' => 'sometimes|string', - 'permissions' => 'sometimes|required|array', - 'admin_permissions' => 'sometimes|required|array', - ]); - - $validator->after(function ($validator) use ($data) { - if (array_key_exists('allowed_ips', $data) && ! empty($data['allowed_ips'])) { - foreach (explode("\n", $data['allowed_ips']) as $ip) { - $ip = trim($ip); - try { - Network::parse($ip); - array_push($this->allowed, $ip); - } catch (\Exception $ex) { - $validator->errors()->add('allowed_ips', 'Could not parse IP <' . $ip . '> because it is in an invalid format.'); - } - } - } - }); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - try { - $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); - $key = Key::create([ - 'user_id' => $this->user->id, - 'public' => str_random(16), - 'secret' => Crypt::encrypt($secretKey), - 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), - 'memo' => $data['memo'], - 'expires_at' => null, - ]); - - $totalPermissions = 0; - $pNodes = Permission::permissions(); - - if (isset($data['permissions'])) { - foreach ($data['permissions'] as $permission) { - $parts = explode('-', $permission); - - if (count($parts) !== 2) { - continue; - } - - list($block, $search) = $parts; - - if (! array_key_exists($block, $pNodes['_user'])) { - continue; - } - - if (! in_array($search, $pNodes['_user'][$block])) { - continue; - } - - $totalPermissions++; - Permission::create([ - 'key_id' => $key->id, - 'permission' => 'user.' . $permission, - ]); - } - } - - if ($this->user->isRootAdmin() && isset($data['admin_permissions'])) { - unset($pNodes['_user']); - - foreach ($data['admin_permissions'] as $permission) { - $parts = explode('-', $permission); - - if (count($parts) !== 2) { - continue; - } - - list($block, $search) = $parts; - - if (! array_key_exists($block, $pNodes)) { - continue; - } - - if (! in_array($search, $pNodes[$block])) { - continue; - } - - $totalPermissions++; - Permission::create([ - 'key_id' => $key->id, - 'permission' => $permission, - ]); - } - } - - if ($totalPermissions < 1) { - throw new DisplayException('No valid permissions were passed.'); - } - - DB::commit(); - - return $secretKey; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Revokes an API key and associated permissions. - * - * @param string $key - * @return void - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function revoke($key) - { - DB::transaction(function () use ($key) { - $model = Key::with('permissions')->where('public', $key)->where('user_id', $this->user->id)->firstOrFail(); - foreach ($model->permissions as &$permission) { - $permission->delete(); - } - - $model->delete(); - }); - } -} diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php new file mode 100644 index 000000000..26ed6544a --- /dev/null +++ b/app/Repositories/Concerns/Searchable.php @@ -0,0 +1,64 @@ +setSearchTerm($term); + } + + /** + * Set the search term to use when requesting all records from + * the model. + * + * @param string|null $term + * @return $this + */ + public function setSearchTerm(string $term = null) + { + if (empty($term)) { + return $this; + } + + $clone = clone $this; + $clone->searchTerm = $term; + + return $clone; + } + + /** + * Determine if a valid search term is set on this repository. + * + * @return bool + */ + public function hasSearchTerm(): bool + { + return ! empty($this->searchTerm); + } + + /** + * Return the search term. + * + * @return string|null + */ + public function getSearchTerm() + { + return $this->searchTerm; + } +} diff --git a/app/Repositories/Daemon/BaseRepository.php b/app/Repositories/Daemon/BaseRepository.php new file mode 100644 index 000000000..e15b17ce1 --- /dev/null +++ b/app/Repositories/Daemon/BaseRepository.php @@ -0,0 +1,153 @@ +app = $app; + $this->nodeRepository = $nodeRepository; + } + + /** + * Set the node model to be used for this daemon connection. + * + * @param \Pterodactyl\Models\Node $node + * @return $this + */ + public function setNode(Node $node) + { + $this->node = $node; + + return $this; + } + + /** + * Return the node model being used. + * + * @return \Pterodactyl\Models\Node|null + */ + public function getNode() + { + return $this->node; + } + + /** + * Set the Server model to use when requesting information from the Daemon. + * + * @param \Pterodactyl\Models\Server $server + * @return $this + */ + public function setServer(Server $server) + { + $this->server = $server; + + return $this; + } + + /** + * Return the Server model. + * + * @return \Pterodactyl\Models\Server|null + */ + public function getServer() + { + return $this->server; + } + + /** + * Set the token to be used in the X-Access-Token header for requests to the daemon. + * + * @param string $token + * @return $this + */ + public function setToken(string $token) + { + $this->token = $token; + + return $this; + } + + /** + * Return the access token being used for requests. + * + * @return string|null + */ + public function getToken() + { + return $this->token; + } + + /** + * Return an instance of the Guzzle HTTP Client to be used for requests. + * + * @param array $headers + * @return \GuzzleHttp\Client + */ + public function getHttpClient(array $headers = []): Client + { + // If no node is set, load the relationship onto the Server model + // and pass that to the setNode function. + if (! $this->getNode() instanceof Node) { + if (! $this->getServer() instanceof Server) { + throw new RuntimeException('An instance of ' . Node::class . ' or ' . Server::class . ' must be set on this repository in order to return a client.'); + } + + $this->getServer()->loadMissing('node'); + $this->setNode($this->getServer()->getRelation('node')); + } + + if ($this->getServer() instanceof Server) { + $headers['X-Access-Server'] = $this->getServer()->uuid; + } + + $headers['X-Access-Token'] = $this->getToken() ?? $this->getNode()->daemonSecret; + + return new Client([ + 'base_uri' => sprintf('%s://%s:%s/v1/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen), + 'timeout' => config('pterodactyl.guzzle.timeout'), + 'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'), + 'headers' => $headers, + ]); + } +} diff --git a/app/Repositories/Daemon/CommandRepository.php b/app/Repositories/Daemon/CommandRepository.php index beb9e8530..31cb6b9b7 100644 --- a/app/Repositories/Daemon/CommandRepository.php +++ b/app/Repositories/Daemon/CommandRepository.php @@ -1,91 +1,25 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\ConnectException; -use Pterodactyl\Exceptions\DisplayException; +use Psr\Http\Message\ResponseInterface; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; -class CommandRepository +class CommandRepository extends BaseRepository implements CommandRepositoryInterface { /** - * The Eloquent Model associated with the requested server. + * Send a command to a server. * - * @var \Pterodactyl\Models\Server + * @param string $command + * @return \Psr\Http\Message\ResponseInterface + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - protected $server; - - /** - * The Eloquent Model associated with the user to run the request as. - * - * @var \Pterodactyl\Models\User|null - */ - protected $user; - - /** - * Constuctor for repository. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User|null $user - * @return void - */ - public function __construct(Server $server, User $user = null) + public function send(string $command): ResponseInterface { - $this->server = $server; - $this->user = $user; - } - - /** - * Sends a command to the daemon. - * - * @param string $command - * @return string - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \GuzzleHttp\Exception\RequestException - */ - public function send($command) - { - // We don't use the user's specific daemon secret here since we - // are assuming that a call to this function has been validated. - try { - $response = $this->server->guzzleClient($this->user)->request('POST', '/server/command', [ - 'http_errors' => false, - 'json' => [ - 'command' => $command, - ], - ]); - - if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Command sending responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); - } - - return $response->getBody(); - } catch (ConnectException $ex) { - throw $ex; - } + return $this->getHttpClient()->request('POST', 'server/command', [ + 'json' => [ + 'command' => $command, + ], + ]); } } diff --git a/app/Repositories/Daemon/ConfigurationRepository.php b/app/Repositories/Daemon/ConfigurationRepository.php new file mode 100644 index 000000000..ff44e3031 --- /dev/null +++ b/app/Repositories/Daemon/ConfigurationRepository.php @@ -0,0 +1,45 @@ +getNode(); + $structure = [ + 'web' => [ + 'listen' => $node->daemonListen, + 'ssl' => [ + 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), + ], + ], + 'sftp' => [ + 'path' => $node->daemonBase, + 'port' => $node->daemonSFTP, + ], + 'remote' => [ + 'base' => config('app.url'), + ], + 'uploads' => [ + 'size_limit' => $node->upload_size, + ], + 'keys' => [ + $node->daemonSecret, + ], + ]; + + return $this->getHttpClient()->request('PATCH', 'config', [ + 'json' => array_merge($structure, $overrides), + ]); + } +} diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php index e789f7469..8350e402f 100644 --- a/app/Repositories/Daemon/FileRepository.php +++ b/app/Repositories/Daemon/FileRepository.php @@ -1,191 +1,114 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Repositories\Daemon; -use Exception; -use GuzzleHttp\Client; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Repositories\HelperRepository; +use stdClass; +use Psr\Http\Message\ResponseInterface; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; -class FileRepository +class FileRepository extends BaseRepository implements FileRepositoryInterface { /** - * The Eloquent Model associated with the requested server. + * Return stat information for a given file. * - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * Constructor. + * @param string $path + * @return \stdClass * - * @param string $uuid - * @return void + * @throws \GuzzleHttp\Exception\RequestException */ - public function __construct($uuid) + public function getFileStat(string $path): stdClass { - $this->server = Server::byUuid($uuid); + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; + + $response = $this->getHttpClient()->request('GET', sprintf( + 'server/file/stat/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); + + return json_decode($response->getBody()); } /** - * Get the contents of a requested file for the server. + * Return the contents of a given file if it can be edited in the Panel. * - * @param string $file - * @return array + * @param string $path + * @return string * * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException */ - public function returnFileContents($file) + public function getContent(string $path): string { - if (empty($file)) { - throw new Exception('Not all parameters were properly passed to the function.'); - } + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; + $response = $this->getHttpClient()->request('GET', sprintf( + 'server/file/f/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); - $res = $this->server->guzzleClient()->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename)); - - $stat = json_decode($res->getBody()); - if ($res->getStatusCode() !== 200 || ! isset($stat->size)) { - throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode()); - } - - if (! in_array($stat->mime, HelperRepository::editableFiles())) { - throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.'); - } - - if ($stat->size > 5000000) { - throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.'); - } - - $res = $this->server->guzzleClient()->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename)); - - $json = json_decode($res->getBody()); - if ($res->getStatusCode() !== 200 || ! isset($json->content)) { - throw new DisplayException('The daemon provided a non-200 error code: HTTP\\' . $res->getStatusCode()); - } - - return [ - 'file' => $json, - 'stat' => $stat, - ]; + return object_get(json_decode($response->getBody()), 'content'); } /** - * Save the contents of a requested file on the daemon. + * Save new contents to a given file. * - * @param string $file - * @param string $content - * @return bool + * @param string $path + * @param string $content + * @return \Psr\Http\Message\ResponseInterface * * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException */ - public function saveFileContents($file, $content) + public function putContent(string $path, string $content): ResponseInterface { - if (empty($file)) { - throw new Exception('A valid file and path must be specified to save a file.'); - } + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; - - $res = $this->server->guzzleClient()->request('POST', '/server/file/save', [ + return $this->getHttpClient()->request('POST', 'server/file/save', [ 'json' => [ - 'path' => rawurlencode($file->dirname . $file->basename), + 'path' => rawurlencode($file['dirname'] . $file['basename']), 'content' => $content, ], ]); - - if ($res->getStatusCode() !== 204) { - throw new DisplayException('An error occured while attempting to save this file. ' . $res->getBody()); - } - - return true; } /** - * Returns a listing of all files and folders within a specified directory on the daemon. + * Return a directory listing for a given path. * - * @param string $directory - * @return object + * @param string $path + * @return array * * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException */ - public function returnDirectoryListing($directory) + public function getDirectory(string $path): array { - if (empty($directory)) { - throw new Exception('A valid directory must be specified in order to list its contents.'); - } + $response = $this->getHttpClient()->request('GET', sprintf('server/directory/%s', rawurlencode($path))); - try { - $res = $this->server->guzzleClient()->request('GET', '/server/directory/' . rawurlencode($directory)); - } catch (\GuzzleHttp\Exception\ClientException $ex) { - $json = json_decode($ex->getResponse()->getBody()); + $contents = json_decode($response->getBody()); + $files = $folders = []; - throw new DisplayException($json->error); - } catch (\GuzzleHttp\Exception\ServerException $ex) { - throw new DisplayException('A remote server error was encountered while attempting to display this directory.'); - } catch (\GuzzleHttp\Exception\ConnectException $ex) { - throw new DisplayException('A ConnectException was encountered: unable to contact daemon.'); - } catch (\Exception $ex) { - throw $ex; - } - - $json = json_decode($res->getBody()); - - // Iterate through results - $files = []; - $folders = []; - foreach ($json as &$value) { + foreach ($contents as $value) { if ($value->directory) { - // @TODO Handle Symlinks - $folders[] = [ + array_push($folders, [ 'entry' => $value->name, - 'directory' => trim($directory, '/'), + 'directory' => trim($path, '/'), 'size' => null, 'date' => strtotime($value->modified), 'mime' => $value->mime, - ]; + ]); } elseif ($value->file) { - $files[] = [ + array_push($files, [ 'entry' => $value->name, - 'directory' => trim($directory, '/'), + 'directory' => trim($path, '/'), 'extension' => pathinfo($value->name, PATHINFO_EXTENSION), - 'size' => HelperRepository::bytesToHuman($value->size), + 'size' => human_readable($value->size), 'date' => strtotime($value->modified), 'mime' => $value->mime, - ]; + ]); } } - return (object) [ + return [ 'files' => $files, 'folders' => $folders, ]; diff --git a/app/Repositories/Daemon/PowerRepository.php b/app/Repositories/Daemon/PowerRepository.php index 925379096..20fc79338 100644 --- a/app/Repositories/Daemon/PowerRepository.php +++ b/app/Repositories/Daemon/PowerRepository.php @@ -1,129 +1,35 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\ConnectException; -use Pterodactyl\Exceptions\DisplayException; +use Psr\Http\Message\ResponseInterface; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; +use Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException; -class PowerRepository +class PowerRepository extends BaseRepository implements PowerRepositoryInterface { /** - * The Eloquent Model associated with the requested server. + * Send a power signal to a server. * - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * The Eloquent Model associated with the user to run the request as. + * @param string $signal + * @return \Psr\Http\Message\ResponseInterface * - * @var \Pterodactyl\Models\User|null + * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException */ - protected $user; - - /** - * Constuctor for repository. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User|null $user - * @return void - */ - public function __construct(Server $server, User $user = null) + public function sendSignal(string $signal): ResponseInterface { - $this->server = $server; - $this->user = $user; - } - - /** - * Sends a power option to the daemon. - * - * @param string $action - * @return string - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function do($action) - { - try { - $response = $this->server->guzzleClient($this->user)->request('PUT', '/server/power', [ - 'http_errors' => false, - 'json' => [ - 'action' => $action, - ], - ]); - - if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Power toggle endpoint responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); - } - - return $response->getBody(); - } catch (ConnectException $ex) { - throw $ex; + switch ($signal) { + case self::SIGNAL_START: + case self::SIGNAL_STOP: + case self::SIGNAL_RESTART: + case self::SIGNAL_KILL: + return $this->getHttpClient()->request('PUT', 'server/power', [ + 'json' => [ + 'action' => $signal, + ], + ]); + default: + throw new InvalidPowerSignalException('The signal "' . $signal . '" is not defined and could not be processed.'); } } - - /** - * Starts a server. - * - * @return void - */ - public function start() - { - $this->do('start'); - } - - /** - * Stops a server. - * - * @return void - */ - public function stop() - { - $this->do('stop'); - } - - /** - * Restarts a server. - * - * @return void - */ - public function restart() - { - $this->do('restart'); - } - - /** - * Kills a server. - * - * @return void - */ - public function kill() - { - $this->do('kill'); - } } diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php new file mode 100644 index 000000000..3af381d1c --- /dev/null +++ b/app/Repositories/Daemon/ServerRepository.php @@ -0,0 +1,127 @@ + $value) { + $structure[$key] = value($value); + } + + return $this->getHttpClient()->request('POST', 'servers', [ + 'json' => $structure, + ]); + } + + /** + * Update server details on the daemon. + * + * @param array $data + * @return \Psr\Http\Message\ResponseInterface + */ + public function update(array $data): ResponseInterface + { + return $this->getHttpClient()->request('PATCH', 'server', [ + 'json' => $data, + ]); + } + + /** + * Mark a server to be reinstalled on the system. + * + * @param array|null $data + * @return \Psr\Http\Message\ResponseInterface + */ + public function reinstall(array $data = null): ResponseInterface + { + return $this->getHttpClient()->request('POST', 'server/reinstall', [ + 'json' => $data ?? [], + ]); + } + + /** + * Mark a server as needing a container rebuild the next time the server is booted. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function rebuild(): ResponseInterface + { + return $this->getHttpClient()->request('POST', 'server/rebuild'); + } + + /** + * Suspend a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function suspend(): ResponseInterface + { + return $this->getHttpClient()->request('POST', 'server/suspend'); + } + + /** + * Un-suspend a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function unsuspend(): ResponseInterface + { + return $this->getHttpClient()->request('POST', 'server/unsuspend'); + } + + /** + * Delete a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function delete(): ResponseInterface + { + return $this->getHttpClient()->request('DELETE', 'servers'); + } + + /** + * Return detials on a specific server. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function details(): ResponseInterface + { + return $this->getHttpClient()->request('GET', 'server'); + } + + /** + * Revoke an access key on the daemon before the time is expired. + * + * @param string|array $key + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException + */ + public function revokeAccessKey($key): ResponseInterface + { + if (is_array($key)) { + return $this->getHttpClient()->request('POST', 'keys', [ + 'json' => $key, + ]); + } + + Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string or array, received %s.'); + + return $this->getHttpClient()->request('DELETE', 'keys/' . $key); + } +} diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php deleted file mode 100644 index f7f428a6f..000000000 --- a/app/Repositories/DatabaseRepository.php +++ /dev/null @@ -1,284 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Crypt; -use Validator; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Database; -use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class DatabaseRepository -{ - /** - * Adds a new database to a specified database host server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Database - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($id, array $data) - { - $server = Server::findOrFail($id); - - $validator = Validator::make($data, [ - 'host' => 'required|exists:database_hosts,id', - 'database' => 'required|regex:/^\w{1,100}$/', - 'connection' => 'required|regex:/^[0-9%.]{1,15}$/', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $host = DatabaseHost::findOrFail($data['host']); - DB::beginTransaction(); - - try { - $database = Database::firstOrNew([ - 'server_id' => $server->id, - 'database_host_id' => $data['host'], - 'database' => sprintf('s%d_%s', $server->id, $data['database']), - ]); - - if ($database->exists) { - throw new DisplayException('A database with those details already exists in the system.'); - } - - $database->username = sprintf('s%d_%s', $server->id, str_random(10)); - $database->remote = $data['connection']; - $database->password = Crypt::encrypt(str_random(20)); - - $database->save(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - - try { - $host->setDynamicConnection(); - - DB::connection('dynamic')->statement(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf( - 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', - $database->username, $database->remote, Crypt::decrypt($database->password) - )); - DB::connection('dynamic')->statement(sprintf( - 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', - $database->database, $database->username, $database->remote - )); - - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - // Save Everything - DB::commit(); - - return $database; - } catch (\Exception $ex) { - try { - DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - } catch (\Exception $ex) { - } - - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates the password for a given database. - * - * @param int $id - * @param string $password - * @return void - * - * @todo Fix logic behind resetting passwords. - */ - public function password($id, $password) - { - $database = Database::with('host')->findOrFail($id); - $database->host->setDynamicConnection(); - - DB::transaction(function () use ($database, $password) { - $database->password = Crypt::encrypt($password); - - // We have to do the whole delete user, create user thing rather than - // SET PASSWORD ... because MariaDB and PHP statements ends up inserting - // a corrupted password. A way around this is strtoupper(sha1(sha1($password, true))) - // but no garuntees that will work correctly with every system. - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement(sprintf( - 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', - $database->username, $database->remote, $password - )); - DB::connection('dynamic')->statement(sprintf( - 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', - $database->database, $database->username, $database->remote - )); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - $database->save(); - }); - } - - /** - * Drops a database from the associated database host. - * - * @param int $id - * @return void - */ - public function drop($id) - { - $database = Database::with('host')->findOrFail($id); - $database->host->setDynamicConnection(); - - DB::transaction(function () use ($database) { - DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - $database->delete(); - }); - } - - /** - * Deletes a database host from the system if it has no associated databases. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $host = DatabaseHost::withCount('databases')->findOrFail($id); - - if ($host->databases_count > 0) { - throw new DisplayException('You cannot delete a database host that has active databases attached to it.'); - } - - $host->delete(); - } - - /** - * Adds a new Database Host to the system. - * - * @param array $data - * @return \Pterodactyl\Models\DatabaseHost - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function add(array $data) - { - if (isset($data['host'])) { - $data['host'] = gethostbyname($data['host']); - } - - $validator = Validator::make($data, [ - 'name' => 'required|string|max:255', - 'host' => 'required|ip|unique:database_hosts,host', - 'port' => 'required|numeric|between:1,65535', - 'username' => 'required|string|max:32', - 'password' => 'required|string', - 'node_id' => 'sometimes|required|exists:nodes,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data) { - $host = new DatabaseHost; - $host->password = Crypt::encrypt($data['password']); - - $host->fill([ - 'name' => $data['name'], - 'host' => $data['host'], - 'port' => $data['port'], - 'username' => $data['username'], - 'max_databases' => null, - 'node_id' => (isset($data['node_id'])) ? $data['node_id'] : null, - ])->save(); - - // Allows us to check that we can connect to things. - $host->setDynamicConnection(); - DB::connection('dynamic')->select('SELECT 1 FROM dual'); - - return $host; - }); - } - - /** - * Updates a Database Host on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\DatabaseHost - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $host = DatabaseHost::findOrFail($id); - - if (isset($data['host'])) { - $data['host'] = gethostbyname($data['host']); - } - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|max:255', - 'host' => 'sometimes|required|ip|unique:database_hosts,host,' . $host->id, - 'port' => 'sometimes|required|numeric|between:1,65535', - 'username' => 'sometimes|required|string|max:32', - 'password' => 'sometimes|required|string', - 'node_id' => 'sometimes|required|exists:nodes,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data, $host) { - if (isset($data['password'])) { - $host->password = Crypt::encrypt($data['password']); - } - $host->fill($data)->save(); - - // Check that we can still connect with these details. - $host->setDynamicConnection(); - DB::connection('dynamic')->select('SELECT 1 FROM dual'); - - return $host; - }); - } -} diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php new file mode 100644 index 000000000..a47134e4c --- /dev/null +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -0,0 +1,175 @@ +getBuilder()->whereIn('id', $ids)->update(['server_id' => $server]); + } + + /** + * Return all of the allocations for a specific node. + * + * @param int $node + * @return \Illuminate\Support\Collection + */ + public function getAllocationsForNode(int $node): Collection + { + return $this->getBuilder()->where('node_id', $node)->get($this->getColumns()); + } + + /** + * Return all of the allocations for a node in a paginated format. + * + * @param int $node + * @param int $perPage + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function getPaginatedAllocationsForNode(int $node, int $perPage = 100): LengthAwarePaginator + { + return $this->getBuilder()->where('node_id', $node)->paginate($perPage, $this->getColumns()); + } + + /** + * Return all of the unique IPs that exist for a given node. + * + * @param int $node + * @return \Illuminate\Support\Collection + */ + public function getUniqueAllocationIpsForNode(int $node): Collection + { + return $this->getBuilder()->where('node_id', $node) + ->groupBy('ip') + ->orderByRaw('INET_ATON(ip) ASC') + ->get($this->getColumns()); + } + + /** + * Return all of the allocations that exist for a node that are not currently + * allocated. + * + * @param int $node + * @return array + */ + public function getUnassignedAllocationIds(int $node): array + { + $results = $this->getBuilder()->select('id')->whereNull('server_id')->where('node_id', $node)->get(); + + return $results->pluck('id')->toArray(); + } + + /** + * Get an array of all allocations that are currently assigned to a given server. + * + * @param int $server + * @return array + */ + public function getAssignedAllocationIds(int $server): array + { + $results = $this->getBuilder()->select('id')->where('server_id', $server)->get(); + + return $results->pluck('id')->toArray(); + } + + /** + * Return a concated result set of node ips that already have at least one + * server assigned to that IP. This allows for filtering out sets for + * dedicated allocation IPs. + * + * If an array of nodes is passed the results will be limited to allocations + * in those nodes. + * + * @param array $nodes + * @return array + */ + public function getDiscardableDedicatedAllocations(array $nodes = []): array + { + $instance = $this->getBuilder()->select( + $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip) as result') + ); + + if (! empty($nodes)) { + $instance->whereIn('node_id', $nodes); + } + + $results = $instance->whereNotNull('server_id') + ->groupBy($this->getBuilder()->raw('CONCAT(node_id, ip)')) + ->get(); + + return $results->pluck('result')->toArray(); + } + + /** + * Return a single allocation from those meeting the requirements. + * + * @param array $nodes + * @param array $ports + * @param bool $dedicated + * @return \Pterodactyl\Models\Allocation|null + */ + public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false) + { + $instance = $this->getBuilder()->whereNull('server_id'); + + if (! empty($nodes)) { + $instance->whereIn('node_id', $nodes); + } + + if (! empty($ports)) { + $instance->where(function (Builder $query) use ($ports) { + $whereIn = []; + foreach ($ports as $port) { + if (is_array($port)) { + $query->orWhereBetween('port', $port); + continue; + } + + $whereIn[] = $port; + } + + if (! empty($whereIn)) { + $query->orWhereIn('port', $whereIn); + } + }); + } + + // If this allocation should not be shared with any other servers get + // the data and modify the query as necessary, + if ($dedicated) { + $discard = $this->getDiscardableDedicatedAllocations($nodes); + + if (! empty($discard)) { + $instance->whereNotIn( + $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip)'), $discard + ); + } + } + + return $instance->inRandomOrder()->first(); + } +} diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php new file mode 100644 index 000000000..7ba0c9982 --- /dev/null +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -0,0 +1,77 @@ +getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->get($this->getColumns()); + } + + /** + * Get all of the application API keys that exist for a specific user. + * + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection + */ + public function getApplicationKeys(User $user): Collection + { + return $this->getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_APPLICATION) + ->get($this->getColumns()); + } + + /** + * Delete an account API key from the panel for a specific user. + * + * @param \Pterodactyl\Models\User $user + * @param string $identifier + * @return int + */ + public function deleteAccountKey(User $user, string $identifier): int + { + return $this->getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->where('identifier', $identifier) + ->delete(); + } + + /** + * Delete an application API key from the panel for a specific user. + * + * @param \Pterodactyl\Models\User $user + * @param string $identifier + * @return int + */ + public function deleteApplicationKey(User $user, string $identifier): int + { + return $this->getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_APPLICATION) + ->where('identifier', $identifier) + ->delete(); + } +} diff --git a/app/Repositories/Eloquent/DaemonKeyRepository.php b/app/Repositories/Eloquent/DaemonKeyRepository.php new file mode 100644 index 000000000..c53f8a471 --- /dev/null +++ b/app/Repositories/Eloquent/DaemonKeyRepository.php @@ -0,0 +1,87 @@ +relationLoaded('server') || $refresh) { + $key->load('server'); + } + + if (! $key->relationLoaded('user') || $refresh) { + $key->load('user'); + } + + return $key; + } + + /** + * Return a daemon key with the associated server relation attached. + * + * @param string $key + * @return \Pterodactyl\Models\DaemonKey + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getKeyWithServer(string $key): DaemonKey + { + Assert::notEmpty($key, 'Expected non-empty string as first argument passed to ' . __METHOD__); + + try { + return $this->getBuilder()->with('server')->where('secret', '=', $key)->firstOrFail($this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Get all of the keys for a specific user including the information needed + * from their server relation for revocation on the daemon. + * + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection + */ + public function getKeysForRevocation(User $user): Collection + { + return $this->getBuilder()->with('node')->where('user_id', $user->id)->get($this->getColumns()); + } + + /** + * Delete an array of daemon keys from the database. Used primarily in + * conjunction with getKeysForRevocation. + * + * @param array $ids + * @return bool|int + */ + public function deleteKeys(array $ids) + { + return $this->getBuilder()->whereIn('id', $ids)->delete(); + } +} diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php new file mode 100644 index 000000000..6bfb94d70 --- /dev/null +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -0,0 +1,51 @@ +getBuilder()->withCount('databases')->with('node')->get(); + } + + /** + * Return a database host with the databases and associated servers + * that are attached to said databases. + * + * @param int $id + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithServers(int $id): DatabaseHost + { + try { + return $this->getBuilder()->with('databases.server')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } +} diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php new file mode 100644 index 000000000..d24dd177e --- /dev/null +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -0,0 +1,191 @@ +database = $database; + } + + /** + * Return the model backing this repository. + * + * @return string + */ + public function model() + { + return Database::class; + } + + /** + * Set the connection name to execute statements against. + * + * @param string $connection + * @return $this + */ + public function setConnection(string $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Return the connection to execute statements aganist. + * + * @return string + */ + public function getConnection(): string + { + return $this->connection; + } + + /** + * Return all of the databases belonging to a server. + * + * @param int $server + * @return \Illuminate\Support\Collection + */ + public function getDatabasesForServer(int $server): Collection + { + return $this->getBuilder()->where('server_id', $server)->get($this->getColumns()); + } + + /** + * Create a new database if it does not already exist on the host with + * the provided details. + * + * @param array $data + * @return \Pterodactyl\Models\Database + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException + */ + public function createIfNotExists(array $data): Database + { + $count = $this->getBuilder()->where([ + ['server_id', '=', array_get($data, 'server_id')], + ['database_host_id', '=', array_get($data, 'database_host_id')], + ['database', '=', array_get($data, 'database')], + ])->count(); + + if ($count > 0) { + throw new DuplicateDatabaseNameException('A database with those details already exists for the specified server.'); + } + + return $this->create($data); + } + + /** + * Create a new database on a given connection. + * + * @param string $database + * @return bool + */ + public function createDatabase(string $database): bool + { + return $this->run(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database)); + } + + /** + * Create a new database user on a given connection. + * + * @param string $username + * @param string $remote + * @param string $password + * @return bool + */ + public function createUser(string $username, string $remote, string $password): bool + { + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); + } + + /** + * Give a specific user access to a given database. + * + * @param string $database + * @param string $username + * @param string $remote + * @return bool + */ + public function assignUserToDatabase(string $database, string $username, string $remote): bool + { + return $this->run(sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, EXECUTE ON `%s`.* TO `%s`@`%s`', + $database, + $username, + $remote + )); + } + + /** + * Flush the privileges for a given connection. + * + * @return bool + */ + public function flush(): bool + { + return $this->run('FLUSH PRIVILEGES'); + } + + /** + * Drop a given database on a specific connection. + * + * @param string $database + * @return bool + */ + public function dropDatabase(string $database): bool + { + return $this->run(sprintf('DROP DATABASE IF EXISTS `%s`', $database)); + } + + /** + * Drop a given user on a specific connection. + * + * @param string $username + * @param string $remote + * @return mixed + */ + public function dropUser(string $username, string $remote): bool + { + return $this->run(sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote)); + } + + /** + * Run the provided statement against the database on a given connection. + * + * @param string $statement + * @return bool + */ + private function run(string $statement): bool + { + return $this->database->connection($this->getConnection())->statement($statement); + } +} diff --git a/app/Repositories/Eloquent/EggRepository.php b/app/Repositories/Eloquent/EggRepository.php new file mode 100644 index 000000000..10d27f284 --- /dev/null +++ b/app/Repositories/Eloquent/EggRepository.php @@ -0,0 +1,102 @@ +getBuilder()->with('variables')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return all eggs and their relations to be used in the daemon API. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAllWithCopyAttributes(): Collection + { + return $this->getBuilder()->with('scriptFrom', 'configFrom')->get($this->getColumns()); + } + + /** + * Return an egg with the scriptFrom and configFrom relations loaded onto the model. + * + * @param int|string $value + * @param string $column + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + 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.'); + + try { + return $this->getBuilder()->with('scriptFrom', 'configFrom')->where($column, '=', $value)->firstOrFail($this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return all of the data needed to export a service. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithExportAttributes(int $id): Egg + { + try { + return $this->getBuilder()->with('scriptFrom', 'configFrom', 'variables')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Confirm a copy script belongs to the same nest as the item trying to use it. + * + * @param int $copyFromId + * @param int $service + * @return bool + */ + public function isCopiableScript(int $copyFromId, int $service): bool + { + return $this->getBuilder()->whereNull('copy_script_from') + ->where('id', '=', $copyFromId) + ->where('nest_id', '=', $service) + ->exists(); + } +} diff --git a/app/Repositories/Eloquent/EggVariableRepository.php b/app/Repositories/Eloquent/EggVariableRepository.php new file mode 100644 index 000000000..9d84b9db1 --- /dev/null +++ b/app/Repositories/Eloquent/EggVariableRepository.php @@ -0,0 +1,36 @@ +getBuilder()->where([ + ['egg_id', '=', $egg], + ['user_viewable', '=', 1], + ['user_editable', '=', 1], + ])->get($this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php new file mode 100644 index 000000000..74ec809fe --- /dev/null +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -0,0 +1,299 @@ +model; + } + + /** + * Return an instance of the builder to use for this repository. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getBuilder() + { + return $this->getModel()->newQuery(); + } + + /** + * Create a new record in the database and return the associated model. + * + * @param array $fields + * @param bool $validate + * @param bool $force + * @return \Illuminate\Database\Eloquent\Model|bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(array $fields, bool $validate = true, bool $force = false) + { + $instance = $this->getBuilder()->newModelInstance(); + ($force) ? $instance->forceFill($fields) : $instance->fill($fields); + + if (! $validate) { + $saved = $instance->skipValidation()->save(); + } else { + if (! $saved = $instance->save()) { + throw new DataValidationException($instance->getValidator()); + } + } + + return ($this->withFresh) ? $instance->fresh() : $saved; + } + + /** + * Find a model that has the specific ID passed. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function find(int $id) + { + try { + return $this->getBuilder()->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Find a model matching an array of where clauses. + * + * @param array $fields + * @return \Illuminate\Support\Collection + */ + public function findWhere(array $fields): Collection + { + return $this->getBuilder()->where($fields)->get($this->getColumns()); + } + + /** + * Find and return the first matching instance for the given fields. + * + * @param array $fields + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function findFirstWhere(array $fields) + { + try { + return $this->getBuilder()->where($fields)->firstOrFail($this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return a count of records matching the passed arguments. + * + * @param array $fields + * @return int + */ + public function findCountWhere(array $fields): int + { + return $this->getBuilder()->where($fields)->count($this->getColumns()); + } + + /** + * Delete a given record from the database. + * + * @param int $id + * @param bool $destroy + * @return int + */ + public function delete(int $id, bool $destroy = false): int + { + return $this->deleteWhere(['id' => $id], $destroy); + } + + /** + * Delete records matching the given attributes. + * + * @param array $attributes + * @param bool $force + * @return int + */ + public function deleteWhere(array $attributes, bool $force = false): int + { + $instance = $this->getBuilder()->where($attributes); + + return ($force) ? $instance->forceDelete() : $instance->delete(); + } + + /** + * Update a given ID with the passed array of fields. + * + * @param int $id + * @param array $fields + * @param bool $validate + * @param bool $force + * @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) + { + try { + $instance = $this->getBuilder()->where('id', $id)->firstOrFail(); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + + ($force) ? $instance->forceFill($fields) : $instance->fill($fields); + + if (! $validate) { + $saved = $instance->skipValidation()->save(); + } else { + if (! $saved = $instance->save()) { + throw new DataValidationException($instance->getValidator()); + } + } + + return ($this->withFresh) ? $instance->fresh() : $saved; + } + + /** + * Perform a mass update where matching records are updated using whereIn. + * This does not perform any model data validation. + * + * @param string $column + * @param array $values + * @param array $fields + * @return int + */ + public function updateWhereIn(string $column, array $values, array $fields): int + { + Assert::notEmpty($column, 'First argument passed to updateWhereIn must be a non-empty string.'); + + return $this->getBuilder()->whereIn($column, $values)->update($fields); + } + + /** + * Update a record if it exists in the database, otherwise create it. + * + * @param array $where + * @param array $fields + * @param bool $validate + * @param bool $force + * @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) + { + 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.'); + } + + try { + $instance = $this->setColumns('id')->findFirstWhere($where); + } catch (RecordNotFoundException $exception) { + return $this->create(array_merge($where, $fields), $validate, $force); + } + + return $this->update($instance->id, $fields, $validate, $force); + } + + /** + * Return all records associated with the given model. + * + * @return \Illuminate\Support\Collection + */ + public function all(): Collection + { + $instance = $this->getBuilder(); + if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { + $instance = $instance->search($this->getSearchTerm()); + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a paginated result set using a search term if set on the repository. + * + * @param int $perPage + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginated(int $perPage): LengthAwarePaginator + { + $instance = $this->getBuilder(); + if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { + $instance = $instance->search($this->getSearchTerm()); + } + + return $instance->paginate($perPage, $this->getColumns()); + } + + /** + * Insert a single or multiple records into the database at once skipping + * validation and mass assignment checking. + * + * @param array $data + * @return bool + */ + public function insert(array $data): bool + { + return $this->getBuilder()->insert($data); + } + + /** + * Insert multiple records into the database and ignore duplicates. + * + * @param array $values + * @return bool + */ + public function insertIgnore(array $values): bool + { + if (empty($values)) { + return true; + } + + foreach ($values as $key => $value) { + ksort($value); + $values[$key] = $value; + } + + $bindings = array_values(array_filter(array_flatten($values, 1), function ($binding) { + return ! $binding instanceof Expression; + })); + + $grammar = $this->getBuilder()->toBase()->getGrammar(); + $table = $grammar->wrapTable($this->getModel()->getTable()); + $columns = $grammar->columnize(array_keys(reset($values))); + + $parameters = collect($values)->map(function ($record) use ($grammar) { + return sprintf('(%s)', $grammar->parameterize($record)); + })->implode(', '); + + $statement = "insert ignore into $table ($columns) values $parameters"; + + return $this->getBuilder()->getConnection()->statement($statement, $bindings); + } +} diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php new file mode 100644 index 000000000..47d5e321a --- /dev/null +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -0,0 +1,79 @@ +getBuilder()->withCount('nodes', 'servers')->get($this->getColumns()); + } + + /** + * Return all of the available locations with the nodes as a relationship. + * + * @return \Illuminate\Support\Collection + */ + public function getAllWithNodes(): Collection + { + return $this->getBuilder()->with('nodes')->get($this->getColumns()); + } + + /** + * Return all of the nodes and their respective count of servers for a location. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithNodes(int $id): Location + { + try { + return $this->getBuilder()->with('nodes.servers')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return a location and the count of nodes in that location. + * + * @param int $id + * @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) { + throw new RecordNotFoundException; + } + } +} diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php new file mode 100644 index 000000000..9c0fcf73c --- /dev/null +++ b/app/Repositories/Eloquent/NestRepository.php @@ -0,0 +1,94 @@ +. + * + * 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 Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +class NestRepository extends EloquentRepository implements NestRepositoryInterface +{ + /** + * Return the model backing this repository. + * + * @return string + */ + public function model() + { + return Nest::class; + } + + /** + * Return a nest or all nests with their associated eggs, variables, and packs. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggs(int $id = null) + { + $instance = $this->getBuilder()->with('eggs.packs', 'eggs.variables'); + + if (! is_null($id)) { + $instance = $instance->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * + * @param int|null $id + * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithCounts(int $id = null) + { + $instance = $this->getBuilder()->withCount(['eggs', 'packs', 'servers']); + + if (! is_null($id)) { + $instance = $instance->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a nest along with its associated eggs and the servers relation on those eggs. + * + * @param int $id + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggServers(int $id): Nest + { + $instance = $this->getBuilder()->with('eggs.servers')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + /* @var Nest $instance */ + return $instance; + } +} diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php new file mode 100644 index 000000000..b4d6ba6b0 --- /dev/null +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -0,0 +1,185 @@ +getBuilder()->select( + $this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(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) { + $maxUsage = $node->{$key}; + if ($node->{$key . '_overallocate'} > 0) { + $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + } + + $percent = ($value / $maxUsage) * 100; + + return [ + $key => [ + 'value' => number_format($value), + 'max' => number_format($maxUsage), + 'percent' => $percent, + 'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'), + ], + ]; + })->toArray(); + } + + /** + * Return all available nodes with a searchable interface. + * + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function getNodeListingData(): LengthAwarePaginator + { + $instance = $this->getBuilder()->with('location')->withCount('servers'); + + if ($this->hasSearchTerm()) { + $instance->search($this->getSearchTerm()); + } + + return $instance->paginate(25, $this->getColumns()); + } + + /** + * Return a single node with location and server information. + * + * @param \Pterodactyl\Models\Node $node + * @param bool $refresh + * @return \Pterodactyl\Models\Node + */ + public function loadLocationAndServerCount(Node $node, bool $refresh = false): Node + { + if (! $node->relationLoaded('location') || $refresh) { + $node->load('location'); + } + + // This is quite ugly and can probably be improved down the road. + // And by probably, I mean it should. + if (is_null($node->servers_count) || $refresh) { + $node->load('servers'); + $node->setRelation('servers_count', count($node->getRelation('servers'))); + unset($node->servers); + } + + return $node; + } + + /** + * Attach a paginated set of allocations to a node mode including + * any servers that are also attached to those allocations. + * + * @param \Pterodactyl\Models\Node $node + * @param bool $refresh + * @return \Pterodactyl\Models\Node + */ + public function loadNodeAllocations(Node $node, bool $refresh = false): Node + { + $node->setRelation('allocations', + $node->allocations()->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')->orderByRaw('INET_ATON(ip) ASC')->orderBy('port', 'asc')->with('server:id,name')->paginate(50) + ); + + return $node; + } + + /** + * Return a node with all of the servers attached to that node. + * + * @param int $id + * @return \Pterodactyl\Models\Node + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getNodeServers(int $id): Node + { + try { + return $this->getBuilder()->with([ + 'servers.user', 'servers.nest', 'servers.egg', + ])->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return a collection of nodes for all locations to use in server creation UI. + * + * @return \Illuminate\Support\Collection + */ + public function getNodesForServerCreation(): Collection + { + return $this->getBuilder()->with('allocations')->get()->map(function (Node $item) { + $filtered = $item->getRelation('allocations')->where('server_id', null)->map(function ($map) { + return collect($map)->only(['id', 'ip', 'port']); + }); + + $item->ports = $filtered->map(function ($map) { + return [ + 'id' => $map['id'], + 'text' => sprintf('%s:%s', $map['ip'], $map['port']), + ]; + })->values(); + + return [ + 'id' => $item->id, + 'text' => $item->name, + 'allocations' => $item->ports, + ]; + })->values(); + } + + /** + * Return the IDs of all nodes that exist in the provided locations and have the space + * available to support the additional disk and memory provided. + * + * @param array $locations + * @param int $disk + * @param int $memory + * @return \Generator + */ + public function getNodesWithResourceUse(array $locations, int $disk, int $memory): Generator + { + $instance = $this->getBuilder() + ->select(['nodes.id', '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') + ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') + ->where('nodes.public', 1); + + if (! empty($locations)) { + $instance->whereIn('nodes.location_id', $locations); + } + + return $instance->groupBy('nodes.id')->cursor(); + } +} diff --git a/app/Repositories/Eloquent/PackRepository.php b/app/Repositories/Eloquent/PackRepository.php new file mode 100644 index 000000000..922e6415a --- /dev/null +++ b/app/Repositories/Eloquent/PackRepository.php @@ -0,0 +1,53 @@ +load(['servers.node', 'servers.user']); + } + + $pack->loadMissing(['servers.node', 'servers.user']); + + return $pack; + } + + /** + * Return a paginated listing of packs with their associated egg and server count. + * + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginateWithEggAndServerCount(): LengthAwarePaginator + { + return $this->getBuilder()->with('egg')->withCount('servers') + ->search($this->getSearchTerm()) + ->paginate(50, $this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php new file mode 100644 index 000000000..ad2fa6386 --- /dev/null +++ b/app/Repositories/Eloquent/PermissionRepository.php @@ -0,0 +1,19 @@ +getBuilder()->withCount('tasks')->where('server_id', '=', $server)->get($this->getColumns()); + } + + /** + * Load the tasks relationship onto the Schedule module if they are not + * already present. + * + * @param \Pterodactyl\Models\Schedule $schedule + * @param bool $refresh + * @return \Pterodactyl\Models\Schedule + */ + public function loadTasks(Schedule $schedule, bool $refresh = false): Schedule + { + if (! $schedule->relationLoaded('tasks') || $refresh) { + $schedule->load('tasks'); + } + + return $schedule; + } + + /** + * Return a schedule model with all of the associated tasks as a relationship. + * + * @param int $schedule + * @return \Pterodactyl\Models\Schedule + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getScheduleWithTasks(int $schedule): Schedule + { + try { + return $this->getBuilder()->with('tasks')->findOrFail($schedule, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return all of the schedules that should be processed. + * + * @param string $timestamp + * @return \Illuminate\Support\Collection + */ + public function getSchedulesToProcess(string $timestamp): Collection + { + return $this->getBuilder()->with('tasks') + ->where('is_active', true) + ->where('next_run_at', '<=', $timestamp) + ->get($this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php new file mode 100644 index 000000000..4bf058a72 --- /dev/null +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -0,0 +1,276 @@ +getBuilder()->with('node', 'user', 'allocation')->search($this->getSearchTerm()); + + return $instance->paginate($paginate, $this->getColumns()); + } + + /** + * Load the egg relations onto the server model. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + */ + public function loadEggRelations(Server $server, bool $refresh = false): Server + { + if (! $server->relationLoaded('egg') || $refresh) { + $server->load('egg.scriptFrom'); + } + + return $server; + } + + /** + * Return a collection of servers with their associated data for rebuild operations. + * + * @param int|null $server + * @param int|null $node + * @return \Illuminate\Support\Collection + */ + public function getDataForRebuild(int $server = null, int $node = null): Collection + { + $instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg', 'node']); + + if (! is_null($server) && is_null($node)) { + $instance = $instance->where('id', '=', $server); + } elseif (is_null($server) && ! is_null($node)) { + $instance = $instance->where('node_id', '=', $node); + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a server model and all variables associated with the server. + * + * @param int $id + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function findWithVariables(int $id): Server + { + try { + return $this->getBuilder()->with('egg.variables', 'variables') + ->where($this->getModel()->getKeyName(), '=', $id) + ->firstOrFail($this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Get the primary allocation for a given server. If a model is passed into + * the function, load the allocation relationship onto it. Otherwise, find and + * return the server from the database. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + */ + public function getPrimaryAllocation(Server $server, bool $refresh = false): Server + { + if (! $server->relationLoaded('allocation') || $refresh) { + $server->load('allocation'); + } + + return $server; + } + + /** + * Return all of the server variables possible and default to the variable + * default if there is no value defined for the specific server requested. + * + * @param int $id + * @param bool $returnAsObject + * @return array|object + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getVariablesWithValues(int $id, bool $returnAsObject = false) + { + try { + $instance = $this->getBuilder()->with('variables', 'egg.variables')->find($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + + $data = []; + $instance->getRelation('egg')->getRelation('variables')->each(function ($item) use (&$data, $instance) { + $display = $instance->getRelation('variables')->where('variable_id', $item->id)->pluck('variable_value')->first(); + + $data[$item->env_variable] = $display ?? $item->default_value; + }); + + if ($returnAsObject) { + return (object) [ + 'data' => $data, + 'server' => $instance, + ]; + } + + return $data; + } + + /** + * Return enough data to be used for the creation of a server via the daemon. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + */ + public function getDataForCreation(Server $server, bool $refresh = false): Server + { + foreach (['allocation', 'allocations', 'pack', 'egg'] as $relation) { + if (! $server->relationLoaded($relation) || $refresh) { + $server->load($relation); + } + } + + return $server; + } + + /** + * Load associated databases onto the server model. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + */ + public function loadDatabaseRelations(Server $server, bool $refresh = false): Server + { + if (! $server->relationLoaded('databases') || $refresh) { + $server->load('databases.host'); + } + + return $server; + } + + /** + * Get data for use when updating a server on the Daemon. Returns an array of + * the egg and pack UUID which are used for build and rebuild. Only loads relations + * if they are missing, or refresh is set to true. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return array + */ + public function getDaemonServiceData(Server $server, bool $refresh = false): array + { + if (! $server->relationLoaded('egg') || $refresh) { + $server->load('egg'); + } + + if (! $server->relationLoaded('pack') || $refresh) { + $server->load('pack'); + } + + return [ + 'egg' => $server->getRelation('egg')->uuid, + 'pack' => is_null($server->getRelation('pack')) ? null : $server->getRelation('pack')->uuid, + ]; + } + + /** + * Return a paginated list of servers that a user can access at a given level. + * + * @param \Pterodactyl\Models\User $user + * @param int $level + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function filterUserAccessServers(User $user, int $level): LengthAwarePaginator + { + $instance = $this->getBuilder()->with(['user']); + + // If access level is set to owner, only display servers + // that the user owns. + if ($level === User::FILTER_LEVEL_OWNER) { + $instance->where('owner_id', $user->id); + } + + // If set to all, display all servers they can access, including + // those they access as an admin. If set to subuser, only return the servers they can access because + // they are owner, or marked as a subuser of the server. + elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) { + $instance->whereIn('id', $this->getUserAccessServers($user->id)); + } + + // If set to admin, only display the servers a user can access + // as an administrator (leaves out owned and subuser of). + elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) { + $instance->whereNotIn('id', $this->getUserAccessServers($user->id)); + } + + return $instance->search($this->getSearchTerm())->paginate(25); + } + + /** + * Return a server by UUID. + * + * @param string $uuid + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getByUuid(string $uuid): Server + { + Assert::notEmpty($uuid, 'Expected non-empty string as first argument passed to ' . __METHOD__); + + try { + return $this->getBuilder()->with('nest', 'node')->where(function ($query) use ($uuid) { + $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + })->firstOrFail($this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return an array of server IDs that a given user can access based + * on owner and subuser permissions. + * + * @param int $user + * @return int[] + */ + private function getUserAccessServers(int $user): array + { + return $this->getBuilder()->select('id')->where('owner_id', $user)->union( + $this->app->make(SubuserRepository::class)->getBuilder()->select('server_id')->where('user_id', $user) + )->pluck('id')->all(); + } +} diff --git a/app/Repositories/Eloquent/ServerVariableRepository.php b/app/Repositories/Eloquent/ServerVariableRepository.php new file mode 100644 index 000000000..d0d5e4dba --- /dev/null +++ b/app/Repositories/Eloquent/ServerVariableRepository.php @@ -0,0 +1,19 @@ +getBuilder()->where('user_id', $user)->get($this->getColumns()); + } + + /** + * Delete a session for a given user. + * + * @param int $user + * @param string $session + * @return null|int + */ + public function deleteUserSession(int $user, string $session) + { + 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 new file mode 100644 index 000000000..0d25a1b80 --- /dev/null +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -0,0 +1,95 @@ +clearCache($key); + $this->withoutFreshModel()->updateOrCreate(['key' => $key], ['value' => $value ?? '']); + + self::$cache[$key] = $value; + } + + /** + * Retrieve a persistent setting from the database. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get(string $key, $default = null) + { + // If item has already been requested return it from the cache. If + // we already know it is missing, immediately return the default value. + if (array_key_exists($key, self::$cache)) { + return self::$cache[$key]; + } elseif (array_key_exists($key, self::$databaseMiss)) { + return value($default); + } + + $instance = $this->getBuilder()->where('key', $key)->first(); + if (is_null($instance)) { + self::$databaseMiss[$key] = true; + + return value($default); + } + + return self::$cache[$key] = $instance->value; + } + + /** + * Remove a key from the database cache. + * + * @param string $key + */ + public function forget(string $key) + { + $this->clearCache($key); + $this->deleteWhere(['key' => $key]); + } + + /** + * Remove a key from the cache. + * + * @param string $key + */ + private function clearCache(string $key) + { + unset(self::$cache[$key], self::$databaseMiss[$key]); + } +} diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php new file mode 100644 index 000000000..0296e0dbd --- /dev/null +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -0,0 +1,83 @@ +relationLoaded('server') || $refresh) { + $subuser->load('server'); + } + + if (! $subuser->relationLoaded('user') || $refresh) { + $subuser->load('user'); + } + + return $subuser; + } + + /** + * Return a subuser with the associated permissions relationship. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @param bool $refresh + * @return \Pterodactyl\Models\Subuser + */ + public function getWithPermissions(Subuser $subuser, bool $refresh = false): Subuser + { + if (! $subuser->relationLoaded('permissions') || $refresh) { + $subuser->load('permissions'); + } + + if (! $subuser->relationLoaded('user') || $refresh) { + $subuser->load('user'); + } + + return $subuser; + } + + /** + * Return a subuser and associated permissions given a user_id and server_id. + * + * @param int $user + * @param int $server + * @return \Pterodactyl\Models\Subuser + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithPermissionsUsingUserAndServer(int $user, int $server): Subuser + { + $instance = $this->getBuilder()->with('permissions')->where([ + ['user_id', '=', $user], + ['server_id', '=', $server], + ])->first(); + + if (is_null($instance)) { + throw new RecordNotFoundException; + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php new file mode 100644 index 000000000..0c1202f59 --- /dev/null +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -0,0 +1,52 @@ +getBuilder()->with('server.user', 'schedule')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Returns the next task in a schedule. + * + * @param int $schedule + * @param int $index + * @return null|\Pterodactyl\Models\Task + */ + public function getNextTask(int $schedule, int $index) + { + return $this->getBuilder()->where('schedule_id', '=', $schedule) + ->where('sequence_id', '=', $index + 1) + ->first($this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php new file mode 100644 index 000000000..008916de2 --- /dev/null +++ b/app/Repositories/Eloquent/UserRepository.php @@ -0,0 +1,57 @@ +getBuilder()->withCount('servers', 'subuserOf') + ->search($this->getSearchTerm()) + ->paginate(50, $this->getColumns()); + } + + /** + * Return all matching models for a user in a format that can be used for dropdowns. + * + * @param string $query + * @return \Illuminate\Support\Collection + */ + public function filterUsersByQuery(string $query): Collection + { + $this->setColumns([ + 'id', 'email', 'username', 'name_first', 'name_last', + ]); + + $instance = $this->getBuilder()->search($query)->get($this->getColumns()); + + return $instance->transform(function ($item) { + $item->md5 = md5(strtolower($item->email)); + + return $item; + }); + } +} diff --git a/app/Repositories/HelperRepository.php b/app/Repositories/HelperRepository.php deleted file mode 100644 index 204480ec2..000000000 --- a/app/Repositories/HelperRepository.php +++ /dev/null @@ -1,73 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -class HelperRepository -{ - /** - * Listing of editable files in the control panel. - * - * @var array - */ - protected static $editable = [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'text/xml', - 'text/css', - 'text/html', - 'text/plain', - 'text/x-perl', - 'text/x-shellscript', - 'inode/x-empty', - ]; - - /** - * Converts from bytes to the largest possible size that is still readable. - * - * @param int $bytes - * @param int $decimals - * @return string - * @deprecated - */ - public static function bytesToHuman($bytes, $decimals = 2) - { - $sz = explode(',', 'B,KB,MB,GB'); - $factor = floor((strlen($bytes) - 1) / 3); - - return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $sz[$factor]; - } - - /** - * Returns array of editable files. - * - * @return array - */ - public static function editableFiles() - { - return self::$editable; - } -} diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/LocationRepository.php deleted file mode 100644 index 5f08cfc17..000000000 --- a/app/Repositories/LocationRepository.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use Validator; -use Pterodactyl\Models\Location; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class LocationRepository -{ - /** - * Creates a new location on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Location - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'short' => 'required|string|between:1,60|unique:locations,short', - 'long' => 'required|string|between:1,255', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return Location::create([ - 'long' => $data['long'], - 'short' => $data['short'], - ]); - } - - /** - * Modifies a location. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Location - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $location = Location::findOrFail($id); - - $validator = Validator::make($data, [ - 'short' => 'sometimes|required|string|between:1,60|unique:locations,short,' . $location->id, - 'long' => 'sometimes|required|string|between:1,255', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $location->fill($data)->save(); - - return $location; - } - - /** - * Deletes a location from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $location = Location::withCount('nodes')->findOrFail($id); - - if ($location->nodes_count > 0) { - throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); - } - - $location->delete(); - } -} diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php deleted file mode 100644 index 2d6fd3c9c..000000000 --- a/app/Repositories/NodeRepository.php +++ /dev/null @@ -1,291 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use IPTools\Network; -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class NodeRepository -{ - /** - * Creates a new node on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Node - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - // Validate Fields - $validator = Validator::make($data, [ - 'name' => 'required|regex:/^([\w .-]{1,100})$/', - 'location_id' => 'required|numeric|min:1|exists:locations,id', - 'public' => 'required|numeric|between:0,1', - 'fqdn' => 'required|string|unique:nodes,fqdn', - 'scheme' => 'required|regex:/^(http(s)?)$/', - 'behind_proxy' => 'required|boolean', - 'memory' => 'required|numeric|min:1', - 'memory_overallocate' => 'required|numeric|min:-1', - 'disk' => 'required|numeric|min:1', - 'disk_overallocate' => 'required|numeric|min:-1', - 'daemonBase' => 'required|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'required|numeric|between:1,65535', - 'daemonListen' => 'required|numeric|between:1,65535', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // Verify the FQDN if using SSL - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { - throw new DisplayException('A fully qualified domain name is required to use a secure comunication method on this node.'); - } - - // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { - throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); - } - - // Should we be nulling the overallocations? - $data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate']; - $data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate']; - - // Set the Secret - $uuid = new UuidService; - $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - - return Models\Node::create($data); - } - - /** - * Updates a node on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Node - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $node = Models\Node::findOrFail($id); - - // Validate Fields - $validator = $validator = Validator::make($data, [ - 'name' => 'regex:/^([\w .-]{1,100})$/', - 'location_id' => 'numeric|min:1|exists:locations,id', - 'public' => 'numeric|between:0,1', - 'fqdn' => 'string|unique:nodes,fqdn,' . $id, - 'scheme' => 'regex:/^(http(s)?)$/', - 'behind_proxy' => 'boolean', - 'memory' => 'numeric|min:1', - 'memory_overallocate' => 'numeric|min:-1', - 'disk' => 'numeric|min:1', - 'disk_overallocate' => 'numeric|min:-1', - 'upload_size' => 'numeric|min:0', - 'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'numeric|between:1,65535', - 'daemonListen' => 'numeric|between:1,65535', - 'reset_secret' => 'sometimes|nullable|accepted', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // Verify the FQDN - if (isset($data['fqdn'])) { - - // Verify the FQDN if using SSL - if ((isset($data['scheme']) && $data['scheme'] === 'https') || (! isset($data['scheme']) && $node->scheme === 'https')) { - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP)) { - throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.'); - } - } - - // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { - throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); - } - } - - // Should we be nulling the overallocations? - if (isset($data['memory_overallocate'])) { - $data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate']; - } - - if (isset($data['disk_overallocate'])) { - $data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate']; - } - - // Set the Secret - if (isset($data['reset_secret']) && ! is_null($data['reset_secret'])) { - $uuid = new UuidService; - $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - unset($data['reset_secret']); - } - - $oldDaemonKey = $node->daemonSecret; - $node->update($data); - try { - $node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [ - 'json' => [ - 'web' => [ - 'listen' => $node->daemonListen, - 'ssl' => [ - 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), - ], - ], - 'sftp' => [ - 'path' => $node->daemonBase, - 'port' => $node->daemonSFTP, - ], - 'remote' => [ - 'base' => config('app.url'), - ], - 'uploads' => [ - 'size_limit' => $node->upload_size, - ], - 'keys' => [ - $node->daemonSecret, - ], - ], - ]); - } catch (\Exception $ex) { - throw new DisplayException('Failed to update the node configuration, however your changes have been saved to the database. You will need to manually update the configuration file for the node to apply these changes.'); - } - } - - /** - * Adds allocations to a provided node. - * - * @param int $id - * @param array $data - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function addAllocations($id, array $data) - { - $node = Models\Node::findOrFail($id); - - $validator = Validator::make($data, [ - 'allocation_ip' => 'required|string', - 'allocation_alias' => 'sometimes|required|string|max:255', - 'allocation_ports' => 'required|array', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $explode = explode('/', $data['allocation_ip']); - if (count($explode) !== 1) { - if (! ctype_digit($explode[1]) || ($explode[1] > 32 || $explode[1] < 25)) { - throw new DisplayException('CIDR notation only allows masks between /32 and /25.'); - } - } - - DB::transaction(function () use ($data, $node) { - foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { - foreach ($data['allocation_ports'] as $port) { - // Determine if this is a valid single port, or a valid port range. - if (! ctype_digit($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { - throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); - } - - if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { - $block = range($matches[1], $matches[2]); - - if (count($block) > 1000) { - throw new DisplayException('Adding more than 1000 ports at once is not supported. Please use a smaller port range.'); - } - - foreach ($block as $unit) { - // Insert into Database - Models\Allocation::firstOrCreate([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $unit, - 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, - 'server_id' => null, - ]); - } - } else { - // Insert into Database - Models\Allocation::firstOrCreate([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $port, - 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, - 'server_id' => null, - ]); - } - } - } - }); - } - - /** - * Deletes a node on the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $node = Models\Node::withCount('servers')->findOrFail($id); - if ($node->servers_count > 0) { - throw new DisplayException('You cannot delete a node with servers currently attached to it.'); - } - - DB::transaction(function () use ($node) { - // Unlink Database Servers - Models\DatabaseHost::where('node_id', $node->id)->update(['node_id' => null]); - - // Delete Allocations - Models\Allocation::where('node_id', $node->id)->delete(); - - // Delete Node - $node->delete(); - }); - } -} diff --git a/app/Repositories/OptionRepository.php b/app/Repositories/OptionRepository.php deleted file mode 100644 index 1a0ce4509..000000000 --- a/app/Repositories/OptionRepository.php +++ /dev/null @@ -1,203 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class OptionRepository -{ - /** - * Creates a new service option on the system. - * - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'service_id' => 'required|numeric|exists:services,id', - 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'tag' => 'required|alpha_num|max:60|unique:service_options,tag', - 'docker_image' => 'sometimes|string|max:255', - 'startup' => 'sometimes|nullable|string', - 'config_from' => 'sometimes|required|numeric|exists:service_options,id', - 'config_startup' => 'required_without:config_from|json', - 'config_stop' => 'required_without:config_from|string|max:255', - 'config_logs' => 'required_without:config_from|json', - 'config_files' => 'required_without:config_from|json', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['config_from'])) { - if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) { - throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); - } - } - - return ServiceOption::create($data); - } - - /** - * Deletes a service option from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id); - - if ($option->servers_count > 0) { - throw new DisplayException('You cannot delete a service option that has servers associated with it.'); - } - - DB::transaction(function () use ($option) { - foreach ($option->variables as $variable) { - (new VariableRepository)->delete($variable->id); - } - - $option->delete(); - }); - } - - /** - * Updates a service option in the database which can then be used - * on nodes. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $option = ServiceOption::findOrFail($id); - - // Due to code limitations (at least when I am writing this currently) - // we have to make an assumption that if config_from is not passed - // that we should be telling it that no config is wanted anymore. - // - // This really is only an issue if we open API access to this function, - // in which case users will always need to pass `config_from` in order - // to keep it assigned. - if (! isset($data['config_from']) && ! is_null($option->config_from)) { - $option->config_from = null; - } - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|max:255', - 'description' => 'sometimes|required|string', - 'tag' => 'sometimes|required|string|max:255|unique:service_options,tag,' . $option->id, - 'docker_image' => 'sometimes|required|string|max:255', - 'startup' => 'sometimes|required|string', - 'config_from' => 'sometimes|required|numeric|exists:service_options,id', - ]); - - $validator->sometimes([ - 'config_startup', 'config_logs', 'config_files', - ], 'required_without:config_from|json', function ($input) use ($option) { - return ! (! $input->config_from && ! is_null($option->config_from)); - }); - - $validator->sometimes('config_stop', 'required_without:config_from|string|max:255', function ($input) use ($option) { - return ! (! $input->config_from && ! is_null($option->config_from)); - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['config_from'])) { - if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) { - throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); - } - } - - $option->fill($data)->save(); - - return $option; - } - - /** - * Updates a service option's scripts in the database. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function scripts($id, array $data) - { - $option = ServiceOption::findOrFail($id); - - $data['script_install'] = empty($data['script_install']) ? null : $data['script_install']; - - $validator = Validator::make($data, [ - 'script_install' => 'sometimes|nullable|string', - 'script_is_privileged' => 'sometimes|required|boolean', - 'script_entry' => 'sometimes|required|string', - 'script_container' => 'sometimes|required|string', - 'copy_script_from' => 'sometimes|nullable|numeric', - ]); - - if (isset($data['copy_script_from']) && ! empty($data['copy_script_from'])) { - $select = ServiceOption::whereNull('copy_script_from')->where([ - ['id', $data['copy_script_from']], - ['service_id', $option->service_id], - ])->first(); - - if (! $select) { - throw new DisplayException('The service option selected to copy a script from either does not exist, or is copying from a higher level.'); - } - } else { - $data['copy_script_from'] = null; - } - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $option->fill($data)->save(); - - return $option; - } -} diff --git a/app/Repositories/PackRepository.php b/app/Repositories/PackRepository.php deleted file mode 100644 index 0a8854465..000000000 --- a/app/Repositories/PackRepository.php +++ /dev/null @@ -1,239 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Uuid; -use Storage; -use Validator; -use Pterodactyl\Models\Pack; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class PackRepository -{ - /** - * Creates a new pack on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'name' => 'required|string', - 'version' => 'required|string', - 'description' => 'sometimes|nullable|string', - 'selectable' => 'sometimes|required|boolean', - 'visible' => 'sometimes|required|boolean', - 'locked' => 'sometimes|required|boolean', - 'option_id' => 'required|exists:service_options,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['file_upload'])) { - if (! $data['file_upload']->isValid()) { - throw new DisplayException('The file provided does not appear to be valid.'); - } - - if (! in_array($data['file_upload']->getMimeType(), ['application/gzip', 'application/x-gzip'])) { - throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetype of application/gzip.'); - } - } - - return DB::transaction(function () use ($data) { - $uuid = new UuidService(); - - $pack = new Pack; - $pack->uuid = $uuid->generate('packs', 'uuid'); - $pack->fill([ - 'option_id' => $data['option_id'], - 'name' => $data['name'], - 'version' => $data['version'], - 'description' => (empty($data['description'])) ? null : $data['description'], - 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']), - 'locked' => isset($data['locked']), - ])->save(); - - if (! $pack->exists) { - throw new DisplayException('Model does not exist after creation. Did an event prevent it from saving?'); - } - - Storage::makeDirectory('packs/' . $pack->uuid); - if (isset($data['file_upload'])) { - $data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); - } - - return $pack; - }); - } - - /** - * Creates a new pack on the system given a template file. - * - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function createWithTemplate(array $data) - { - if (! isset($data['file_upload'])) { - throw new DisplayException('No template file was found submitted with this request.'); - } - - if (! $data['file_upload']->isValid()) { - throw new DisplayException('The file provided does not appear to be valid.'); - } - - if (! in_array($data['file_upload']->getMimeType(), [ - 'application/zip', - 'text/plain', - 'application/json', - ])) { - throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetypes of application/zip or application/json.'); - } - - if ($data['file_upload']->getMimeType() === 'application/zip') { - $zip = new \ZipArchive; - if (! $zip->open($data['file_upload']->path())) { - throw new DisplayException('The uploaded archive was unable to be opened.'); - } - - $isTar = $zip->locateName('archive.tar.gz'); - - if (! $zip->locateName('import.json') || ! $isTar) { - throw new DisplayException('This contents of the provided archive were in an invalid format.'); - } - - $json = json_decode($zip->getFromName('import.json')); - $pack = $this->create([ - 'name' => $json->name, - 'version' => $json->version, - 'description' => $json->description, - 'option_id' => $data['option_id'], - 'selectable' => $json->selectable, - 'visible' => $json->visible, - 'locked' => $json->locked, - ]); - - if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { - $pack->delete(); - throw new DisplayException('Unable to extract the archive file to the correct location.'); - } - - $zip->close(); - - return $pack; - } else { - $json = json_decode(file_get_contents($data['file_upload']->path())); - - return $this->create([ - 'name' => $json->name, - 'version' => $json->version, - 'description' => $json->description, - 'option_id' => $data['option_id'], - 'selectable' => $json->selectable, - 'visible' => $json->visible, - 'locked' => $json->locked, - ]); - } - } - - /** - * Updates a pack on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string', - 'option_id' => 'sometimes|required|exists:service_options,id', - 'version' => 'sometimes|required|string', - 'description' => 'sometimes|string', - 'selectable' => 'sometimes|required|boolean', - 'visible' => 'sometimes|required|boolean', - 'locked' => 'sometimes|required|boolean', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $pack = Pack::withCount('servers')->findOrFail($id); - - if ($pack->servers_count > 0 && (isset($data['option_id']) && (int) $data['option_id'] !== $pack->option_id)) { - throw new DisplayException('You cannot modify the associated option if servers are attached to a pack.'); - } - - $pack->fill([ - 'name' => isset($data['name']) ? $data['name'] : $pack->name, - 'option_id' => isset($data['option_id']) ? $data['option_id'] : $pack->option_id, - 'version' => isset($data['version']) ? $data['version'] : $pack->version, - 'description' => (empty($data['description'])) ? null : $data['description'], - 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']), - 'locked' => isset($data['locked']), - ])->save(); - - return $pack; - } - - /** - * Deletes a pack and files from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $pack = Pack::withCount('servers')->findOrFail($id); - - if ($pack->servers_count > 0) { - throw new DisplayException('Cannot delete a pack from the system if servers are assocaited with it.'); - } - - DB::transaction(function () use ($pack) { - $pack->delete(); - Storage::deleteDirectory('packs/' . $pack->uuid); - }); - } -} diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php new file mode 100644 index 000000000..c3014a31a --- /dev/null +++ b/app/Repositories/Repository.php @@ -0,0 +1,137 @@ +app = $application; + + $this->initalizeModel($this->model()); + } + + /** + * Return the model backing this repository. + * + * @return string|\Closure|object + */ + abstract public function model(); + + /** + * Return the model being used for this repository. + * + * @return mixed + */ + public function getModel() + { + return $this->model; + } + + /** + * Setup column selection functionality. + * + * @param array|string $columns + * @return $this + */ + public function setColumns($columns = ['*']) + { + $clone = clone $this; + $clone->columns = is_array($columns) ? $columns : func_get_args(); + + return $clone; + } + + /** + * Return the columns to be selected in the repository call. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Stop repository update functions from returning a fresh + * model when changes are committed. + * + * @return $this + */ + public function withoutFreshModel() + { + return $this->setFreshModel(false); + } + + /** + * Return a fresh model with a repository updates a model. + * + * @return $this + */ + public function withFreshModel() + { + return $this->setFreshModel(true); + } + + /** + * Set wether or not the repository should return a fresh model + * when changes are committed. + * + * @param bool $fresh + * @return $this + */ + public function setFreshModel(bool $fresh = true) + { + $clone = clone $this; + $clone->withFresh = $fresh; + + return $clone; + } + + /** + * Take the provided model and make it accessible to the rest of the repository. + * + * @param array $model + * @return mixed + */ + protected function initalizeModel(...$model) + { + switch (count($model)) { + case 1: + return $this->model = $this->app->make($model[0]); + case 2: + return $this->model = call_user_func([$this->app->make($model[0]), $model[1]]); + default: + throw new InvalidArgumentException('Model must be a FQCN or an array with a count of two.'); + } + } +} diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php deleted file mode 100644 index 374de1b07..000000000 --- a/app/Repositories/ServerRepository.php +++ /dev/null @@ -1,1036 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Crypt; -use Validator; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Pack; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\Allocation; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Models\ServerVariable; -use Pterodactyl\Models\ServiceVariable; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Services\DeploymentService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServerRepository -{ - /** - * An array of daemon permission to assign to this server. - * - * @var array - */ - protected $daemonPermissions = [ - 's:*', - ]; - - /** - * Generates a SFTP username for a server given a server name. - * format: mumble_67c7a4b0. - * - * @param string $name - * @param null|string $identifier - * @return string - */ - protected function generateSFTPUsername($name, $identifier = null) - { - if (is_null($identifier) || ! ctype_alnum($identifier)) { - $unique = str_random(8); - } else { - if (strlen($identifier) < 8) { - $unique = $identifier . str_random((8 - strlen($identifier))); - } else { - $unique = substr($identifier, 0, 8); - } - } - - // Filter the Server Name - $name = trim(preg_replace('/[^\w]+/', '', $name), '_'); - $name = (strlen($name) < 1) ? str_random(6) : $name; - - return strtolower(substr($name, 0, 6) . '_' . $unique); - } - - /** - * Adds a new server to the system. - * - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\AutoDeploymentException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'user_id' => 'required|exists:users,id', - 'name' => 'required|regex:/^([\w .-]{1,200})$/', - 'description' => 'sometimes|nullable|string', - 'memory' => 'required|numeric|min:0', - 'swap' => 'required|numeric|min:-1', - 'io' => 'required|numeric|min:10|max:1000', - 'cpu' => 'required|numeric|min:0', - 'disk' => 'required|numeric|min:0', - 'service_id' => 'required|numeric|min:1|exists:services,id', - 'option_id' => 'required|numeric|min:1|exists:service_options,id', - 'location_id' => 'required|numeric|min:1|exists:locations,id', - 'pack_id' => 'sometimes|nullable|numeric|min:0', - 'custom_container' => 'string', - 'startup' => 'string', - 'auto_deploy' => 'sometimes|required|accepted', - 'custom_id' => 'sometimes|required|numeric|unique:servers,id', - 'skip_scripts' => 'sometimes|required|boolean', - ]); - - $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) { - return ! ($input->auto_deploy); - }); - - $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) { - return ! ($input->auto_deploy); - }); - - $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) { - return ! ($input->auto_deploy); - }); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $user = User::findOrFail($data['user_id']); - - $deployment = false; - if (isset($data['auto_deploy'])) { - $deployment = new DeploymentService; - - if (isset($data['location_id'])) { - $deployment->setLocation($data['location_id']); - } - - $deployment->setMemory($data['memory'])->setDisk($data['disk'])->select(); - } - - $node = (! $deployment) ? Node::findOrFail($data['node_id']) : $deployment->node(); - - // Verify IP & Port are a.) free and b.) assigned to the node. - // We know the node exists because of 'exists:nodes,id' in the validation - if (! $deployment) { - $allocation = Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first(); - } else { - $allocation = $deployment->allocation(); - } - - // Something failed in the query, either that combo doesn't exist, or it is in use. - if (! $allocation) { - throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.'); - } - - // Validate those Service Option Variables - // We know the service and option exists because of the validation. - // We need to verify that the option exists for the service, and then check for - // any required variable fields. (fields are labeled env_) - $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); - if (! $option) { - throw new DisplayException('The requested service option does not exist for the specified service.'); - } - - // Validate the Pack - if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } else { - $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); - if (! $pack) { - throw new DisplayException('The requested service pack does not seem to exist for this combination.'); - } - } - - // Load up the Service Information - $service = Service::find($option->service_id); - - // Check those Variables - $variables = ServiceVariable::where('option_id', $data['option_id'])->get(); - $variableList = []; - if ($variables) { - foreach ($variables as $variable) { - - // Is the variable required? - if (! isset($data['env_' . $variable->env_variable])) { - if ($variable->required) { - throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); - } - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $variable->default_value, - ]; - continue; - } - - // Check aganist Regex Pattern - if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) { - throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); - } - - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $data['env_' . $variable->env_variable], - ]; - continue; - } - } - - // Check Overallocation - if (! $deployment) { - if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(); - - // Check memory limits - if (is_numeric($node->memory_overallocate)) { - $newMemory = $totals->memory + $data['memory']; - $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100))); - if ($newMemory > $memoryLimit) { - throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.'); - } - } - - // Check Disk Limits - if (is_numeric($node->disk_overallocate)) { - $newDisk = $totals->disk + $data['disk']; - $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100))); - if ($newDisk > $diskLimit) { - throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.'); - } - } - } - } - - DB::beginTransaction(); - - try { - $uuid = new UuidService; - - // Add Server to the Database - $server = new Server; - $genUuid = $uuid->generate('servers', 'uuid'); - $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid); - - if (isset($data['custom_id'])) { - $server->id = $data['custom_id']; - } - - $server->fill([ - 'uuid' => $genUuid, - 'uuidShort' => $genShortUuid, - 'node_id' => $node->id, - 'name' => $data['name'], - 'description' => $data['description'], - 'skip_scripts' => isset($data['skip_scripts']), - 'suspended' => false, - 'owner_id' => $user->id, - 'memory' => $data['memory'], - 'swap' => $data['swap'], - 'disk' => $data['disk'], - 'io' => $data['io'], - 'cpu' => $data['cpu'], - 'oom_disabled' => isset($data['oom_disabled']), - 'allocation_id' => $allocation->id, - 'service_id' => $data['service_id'], - 'option_id' => $data['option_id'], - 'pack_id' => $data['pack_id'], - 'startup' => $data['startup'], - 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), - 'image' => (isset($data['custom_container']) && ! empty($data['custom_container'])) ? $data['custom_container'] : $option->docker_image, - 'username' => $this->generateSFTPUsername($data['name'], $genShortUuid), - 'sftp_password' => Crypt::encrypt('not set'), - ]); - $server->save(); - - // Mark Allocation in Use - $allocation->server_id = $server->id; - $allocation->save(); - - // Add Additional Allocations - if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { - foreach ($data['allocation_additional'] as $allocation) { - $model = Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first(); - if (! $model) { - continue; - } - - $model->server_id = $server->id; - $model->save(); - } - } - - foreach ($variableList as $item) { - ServerVariable::create([ - 'server_id' => $server->id, - 'variable_id' => $item['id'], - 'variable_value' => $item['val'], - ]); - } - - $environment = $this->parseVariables($server); - $server->load('allocation', 'allocations'); - - $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [ - 'json' => [ - 'uuid' => (string) $server->uuid, - 'user' => $server->username, - 'build' => [ - 'default' => [ - 'ip' => $server->allocation->ip, - 'port' => $server->allocation->port, - ], - 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(), - 'env' => $environment->pluck('value', 'variable')->toArray(), - 'memory' => (int) $server->memory, - 'swap' => (int) $server->swap, - 'io' => (int) $server->io, - 'cpu' => (int) $server->cpu, - 'disk' => (int) $server->disk, - 'image' => $server->image, - ], - 'service' => [ - 'type' => $service->folder, - 'option' => $option->tag, - 'pack' => (isset($pack)) ? $pack->uuid : null, - 'skip_scripts' => $server->skip_scripts, - ], - 'keys' => [ - (string) $server->daemonSecret => $this->daemonPermissions, - ], - 'rebuild' => false, - 'start_on_completion' => isset($data['start_on_completion']), - ], - ]); - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the details for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateDetails($id, array $data) - { - $uuid = new UuidService; - $resetDaemonKey = false; - - // Validate Fields - $validator = Validator::make($data, [ - 'owner_id' => 'sometimes|required|integer|exists:users,id', - 'name' => 'sometimes|required|regex:([\w .-]{1,200})', - 'description' => 'sometimes|nullable|string', - 'reset_token' => 'sometimes|required|accepted', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $server = Server::with('user')->findOrFail($id); - - // Update daemon secret if it was passed. - if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) { - $oldDaemonKey = $server->daemonSecret; - $server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); - $resetDaemonKey = true; - } - - // Save our changes - $server->fill($data)->save(); - - // Do we need to update? If not, return successful. - if (! $resetDaemonKey) { - return DB::commit(); - } - - $res = $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'exceptions' => false, - 'json' => [ - 'keys' => [ - (string) $oldDaemonKey => [], - (string) $server->daemonSecret => $this->daemonPermissions, - ], - ], - ]); - - if ($res->getStatusCode() === 204) { - DB::commit(); - - return $server; - } else { - throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode()); - } - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the container for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateContainer($id, array $data) - { - $validator = Validator::make($data, [ - 'docker_image' => 'required|string', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - try { - $server = Server::findOrFail($id); - - $server->image = $data['docker_image']; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => [ - 'image' => $server->image, - ], - ], - ]); - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the build details for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function changeBuild($id, array $data) - { - $validator = Validator::make($data, [ - 'allocation_id' => 'sometimes|required|exists:allocations,id', - 'add_allocations' => 'sometimes|required|array', - 'remove_allocations' => 'sometimes|required|array', - 'memory' => 'sometimes|required|integer|min:0', - 'swap' => 'sometimes|required|integer|min:-1', - 'io' => 'sometimes|required|integer|min:10|max:1000', - 'cpu' => 'sometimes|required|integer|min:0', - 'disk' => 'sometimes|required|integer|min:0', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $server = Server::with('allocation', 'allocations')->findOrFail($id); - $newBuild = []; - $newAllocations = []; - - if (isset($data['allocation_id'])) { - if ((int) $data['allocation_id'] !== $server->allocation_id) { - $selection = $server->allocations->where('id', $data['allocation_id'])->first(); - if (! $selection) { - throw new DisplayException('The requested default connection is not allocated to this server.'); - } - - $server->allocation_id = $selection->id; - $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port]; - - $server->load('allocation'); - } - } - - $newPorts = false; - $firstNewAllocation = null; - // Add Assignments - if (isset($data['add_allocations'])) { - foreach ($data['add_allocations'] as $allocation) { - $model = Allocation::where('id', $allocation)->whereNull('server_id')->first(); - if (! $model) { - continue; - } - - $newPorts = true; - $firstNewAllocation = $firstNewAllocation ?? $model; - $model->update([ - 'server_id' => $server->id, - ]); - } - - $server->load('allocations'); - } - - // Remove Assignments - if (isset($data['remove_allocations'])) { - foreach ($data['remove_allocations'] as $allocation) { - // Can't remove the assigned IP/Port combo - if ((int) $allocation === $server->allocation_id) { - // No New Allocation - if (is_null($firstNewAllocation)) { - continue; - } - - // New Allocation, set as the default. - $server->allocation_id = $firstNewAllocation->id; - $newBuild['default'] = ['ip' => $firstNewAllocation->ip, 'port' => $firstNewAllocation->port]; - } - - $newPorts = true; - Allocation::where('id', $allocation)->where('server_id', $server->id)->update([ - 'server_id' => null, - ]); - } - - $server->load('allocations'); - } - - if ($newPorts) { - $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(); - - $newBuild['env|overwrite'] = $this->parseVariables($server)->pluck('value', 'variable')->toArray(); - } - - // @TODO: verify that server can be set to this much memory without - // going over node limits. - if (isset($data['memory']) && $server->memory !== (int) $data['memory']) { - $server->memory = $data['memory']; - $newBuild['memory'] = (int) $server->memory; - } - - if (isset($data['swap']) && $server->swap !== (int) $data['swap']) { - $server->swap = $data['swap']; - $newBuild['swap'] = (int) $server->swap; - } - - // @TODO: verify that server can be set to this much disk without - // going over node limits. - if (isset($data['disk']) && $server->disk !== (int) $data['disk']) { - $server->disk = $data['disk']; - $newBuild['disk'] = (int) $server->disk; - } - - if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) { - $server->cpu = $data['cpu']; - $newBuild['cpu'] = (int) $server->cpu; - } - - if (isset($data['io']) && $server->io !== (int) $data['io']) { - $server->io = $data['io']; - $newBuild['io'] = (int) $server->io; - } - - // Try save() here so if it fails we haven't contacted the daemon - // This won't be committed unless the HTTP request succeedes anyways - $server->save(); - - if (! empty($newBuild)) { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => $newBuild, - ], - ]); - } - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Process the variables for a server, and save to the database. - * - * @param \Pterodactyl\Models\Server $server - * @param array $data - * @param bool $admin - * @return \Illuminate\Support\Collection - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - protected function processVariables(Server $server, $data, $admin = false) - { - $server->load('option.variables'); - - if ($admin) { - $server->startup = $data['startup']; - $server->save(); - } - - if ($server->option->variables) { - foreach ($server->option->variables as &$variable) { - $set = isset($data['env_' . $variable->id]); - - // If user is not an admin and are trying to edit a non-editable field - // or an invisible field just silently skip the variable. - if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { - continue; - } - - // Perform Field Validation - $validator = Validator::make([ - 'variable_value' => ($set) ? $data['env_' . $variable->id] : null, - ], [ - 'variable_value' => $variable->rules, - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode( - collect([ - 'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'], - ])->merge($validator->errors()->toArray()) - )); - } - - $svar = ServerVariable::firstOrNew([ - 'server_id' => $server->id, - 'variable_id' => $variable->id, - ]); - - // Set the value; if one was not passed set it to the default value - if ($set) { - $svar->variable_value = $data['env_' . $variable->id]; - - // Not passed, check if this record exists if so keep value, otherwise set default - } else { - $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; - } - - $svar->save(); - } - } - - return $this->parseVariables($server); - } - - /** - * Parse the variables and return in a standardized format. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Support\Collection - */ - protected function parseVariables(Server $server) - { - // Reload Variables - $server->load('variables'); - - $parsed = $server->option->variables->map(function ($item, $key) use ($server) { - $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - - return [ - 'variable' => $item->env_variable, - 'value' => (! is_null($display)) ? $display : $item->default_value, - ]; - }); - - $merge = [[ - 'variable' => 'STARTUP', - 'value' => $server->startup, - ], [ - 'variable' => 'P_VARIABLE__LOCATION', - 'value' => $server->location->short, - ]]; - - $allocations = $server->allocations->where('id', '!=', $server->allocation_id); - $i = 0; - - foreach ($allocations as $allocation) { - $merge[] = [ - 'variable' => 'ALLOC_' . $i . '__PORT', - 'value' => $allocation->port, - ]; - - $i++; - } - - if ($parsed->count() === 0) { - return collect($merge); - } - - return $parsed->merge($merge); - } - - /** - * Update the startup details for a server. - * - * @param int $id - * @param array $data - * @param bool $admin - * @return bool - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateStartup($id, array $data, $admin = false) - { - $server = Server::with('variables', 'option.variables')->findOrFail($id); - $hasServiceChanges = false; - - if ($admin) { - // User is an admin, lots of things to do here. - $validator = Validator::make($data, [ - 'startup' => 'required|string', - 'skip_scripts' => 'sometimes|required|boolean', - 'service_id' => 'required|numeric|min:1|exists:services,id', - 'option_id' => 'required|numeric|min:1|exists:service_options,id', - 'pack_id' => 'sometimes|nullable|numeric|min:0', - ]); - - if ((int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if ( - $server->service_id != $data['service_id'] || - $server->option_id != $data['option_id'] || - $server->pack_id != $data['pack_id'] - ) { - $hasServiceChanges = true; - } - } - - // If user isn't an administrator, this function is being access from the front-end - // Just try to update specific variables. - if (! $admin || ! $hasServiceChanges) { - return DB::transaction(function () use ($admin, $data, $server) { - $environment = $this->processVariables($server, $data, $admin); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => [ - 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), - ], - ], - ]); - - return false; - }); - } - - // Validate those Service Option Variables - // We know the service and option exists because of the validation. - // We need to verify that the option exists for the service, and then check for - // any required variable fields. (fields are labeled env_) - $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); - if (! $option) { - throw new DisplayException('The requested service option does not exist for the specified service.'); - } - - // Validate the Pack - if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } else { - $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); - if (! $pack) { - throw new DisplayException('The requested service pack does not seem to exist for this combination.'); - } - } - - return DB::transaction(function () use ($admin, $data, $server) { - $server->installed = 0; - $server->service_id = $data['service_id']; - $server->option_id = $data['option_id']; - $server->pack_id = $data['pack_id']; - $server->skip_scripts = isset($data['skip_scripts']); - $server->save(); - - $server->variables->each->delete(); - - $server->load('service', 'pack'); - - // Send New Environment - $environment = $this->processVariables($server, $data, $admin); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/reinstall', [ - 'json' => [ - 'build' => [ - 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), - ], - 'service' => [ - 'type' => $server->option->service->folder, - 'option' => $server->option->tag, - 'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null, - 'skip_scripts' => $server->skip_scripts, - ], - ], - ]); - - return true; - }); - } - - /** - * Delete a server from the system permanetly. - * - * @param int $id - * @param bool $force - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id, $force = false) - { - $server = Server::with('node', 'allocations', 'variables')->findOrFail($id); - - // Due to MySQL lockouts if the daemon response fails, we need to - // delete the server from the daemon first. If it succeedes and then - // MySQL fails, users just need to force delete the server. - // - // If this is a force delete, continue anyways. - try { - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('DELETE', '/servers'); - } catch (ClientException $ex) { - // Exception is thrown on 4XX HTTP errors, so catch and determine - // if we should continue, or if there is a permissions error. - // - // Daemon throws a 404 if the server doesn't exist, if that is returned - // continue with deletion, even if not a force deletion. - $response = $ex->getResponse(); - if ($ex->getResponse()->getStatusCode() !== 404 && ! $force) { - throw new DisplayException($ex->getMessage()); - } - } catch (TransferException $ex) { - if (! $force) { - throw new DisplayException($ex->getMessage()); - } - } catch (\Exception $ex) { - throw $ex; - } - - DB::transaction(function () use ($server) { - $server->allocations->each(function ($item) { - $item->server_id = null; - $item->save(); - }); - - $server->variables->each->delete(); - - $server->load('subusers.permissions'); - $server->subusers->each(function ($subuser) { - $subuser->permissions->each->delete(); - $subuser->delete(); - }); - - $server->tasks->each->delete(); - - // Delete Databases - // This is the one un-recoverable point where - // transactions will not save us. - $repository = new DatabaseRepository; - $server->databases->each(function ($item) use ($repository) { - $repository->drop($item->id); - }); - - // Fully delete the server. - $server->delete(); - }); - } - - /** - * Toggle the install status of a serve. - * - * @param int $id - * @return bool - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function toggleInstall($id) - { - $server = Server::findOrFail($id); - if ($server->installed > 1) { - throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.'); - } - $server->installed = ! $server->installed; - - return $server->save(); - } - - /** - * Suspends or unsuspends a server. - * - * @param int $id - * @param bool $unsuspend - * @return void - */ - public function toggleAccess($id, $unsuspend = true) - { - $server = Server::with('node')->findOrFail($id); - - DB::transaction(function () use ($server, $unsuspend) { - if ( - (! $unsuspend && $server->suspended) || - ($unsuspend && ! $server->suspended) - ) { - return true; - } - - $server->suspended = ! $unsuspend; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', ($unsuspend) ? '/server/unsuspend' : '/server/suspend'); - }); - } - - /** - * Updates the SFTP password for a server. - * - * @param int $id - * @param string $password - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateSFTPPassword($id, $password) - { - $server = Server::with('node')->findOrFail($id); - - $validator = Validator::make(['password' => $password], [ - 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::transaction(function () use ($password, $server) { - $server->sftp_password = Crypt::encrypt($password); - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', '/server/password', [ - 'json' => ['password' => $password], - ]); - }); - } - - /** - * Marks a server for reinstallation on the node. - * - * @param int $id - * @return void - */ - public function reinstall($id) - { - $server = Server::with('node')->findOrFail($id); - - DB::transaction(function () use ($server) { - $server->installed = 0; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', '/server/reinstall'); - }); - } -} diff --git a/app/Repositories/ServiceRepository.php b/app/Repositories/ServiceRepository.php deleted file mode 100644 index a0d1716cc..000000000 --- a/app/Repositories/ServiceRepository.php +++ /dev/null @@ -1,135 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\Service; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServiceRepository -{ - /** - * Creates a new service on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Service - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|nullable|string', - 'folder' => 'required|unique:services,folder|regex:/^[\w.-]{1,50}$/', - 'startup' => 'required|nullable|string', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data) { - $service = new Service; - $service->author = config('pterodactyl.service.author'); - $service->fill([ - 'name' => $data['name'], - 'description' => (isset($data['description'])) ? $data['description'] : null, - 'folder' => $data['folder'], - 'startup' => (isset($data['startup'])) ? $data['startup'] : null, - 'index_file' => Service::defaultIndexFile(), - ])->save(); - - // It is possible for an event to return false or throw an exception - // which won't necessarily be detected by this transaction. - // - // This check ensures the model was actually saved. - if (! $service->exists) { - throw new \Exception('Service model was created however the response appears to be invalid. Did an event fire wrongly?'); - } - - return $service; - }); - } - - /** - * Updates a service. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Service - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $service = Service::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|min:1|max:255', - 'description' => 'sometimes|required|nullable|string', - 'folder' => 'sometimes|required|regex:/^[\w.-]{1,50}$/', - 'startup' => 'sometimes|required|nullable|string', - 'index_file' => 'sometimes|required|string', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data, $service) { - $service->fill($data)->save(); - - return $service; - }); - } - - /** - * Deletes a service and associated files and options. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $service = Service::withCount('servers')->with('options')->findOrFail($id); - - if ($service->servers_count > 0) { - throw new DisplayException('You cannot delete a service that has servers associated with it.'); - } - - DB::transaction(function () use ($service) { - foreach ($service->options as $option) { - (new OptionRepository)->delete($option->id); - } - - $service->delete(); - }); - } -} diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/SubuserRepository.php deleted file mode 100644 index 7a2aef8cc..000000000 --- a/app/Repositories/SubuserRepository.php +++ /dev/null @@ -1,264 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; -use Pterodactyl\Services\UuidService; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class SubuserRepository -{ - /** - * Core permissions required for every subuser on the daemon. - * Without this we cannot connect the websocket or get basic - * information about the server. - * - * @var array - */ - protected $coreDaemonPermissions = [ - 's:get', - 's:console', - ]; - - /** - * Creates a new subuser on the server. - * - * @param int $sid - * @param array $data - * @return \Pterodactyl\Models\Subuser - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($sid, array $data) - { - $server = Server::with('node')->findOrFail($sid); - - $validator = Validator::make($data, [ - 'permissions' => 'required|array', - 'email' => 'required|email', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - // Determine if this user exists or if we need to make them an account. - $user = User::where('email', $data['email'])->first(); - if (! $user) { - try { - $repo = new UserRepository; - $user = $repo->create([ - 'email' => $data['email'], - 'username' => str_random(8), - 'name_first' => 'Unassigned', - 'name_last' => 'Name', - 'root_admin' => false, - ]); - } catch (\Exception $ex) { - throw $ex; - } - } elseif ($server->owner_id === $user->id) { - throw new DisplayException('You cannot add the owner of a server as a subuser.'); - } elseif (Subuser::select('id')->where('user_id', $user->id)->where('server_id', $server->id)->first()) { - throw new DisplayException('A subuser with that email already exists for this server.'); - } - - $uuid = new UuidService; - $subuser = Subuser::create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'daemonSecret' => (string) $uuid->generate('servers', 'uuid'), - ]); - - $perms = Permission::listPermissions(true); - $daemonPermissions = $this->coreDaemonPermissions; - - foreach ($data['permissions'] as $permission) { - if (array_key_exists($permission, $perms)) { - // Build the daemon permissions array for sending. - if (! is_null($perms[$permission])) { - array_push($daemonPermissions, $perms[$permission]); - } - - Permission::create([ - 'subuser_id' => $subuser->id, - 'permission' => $permission, - ]); - } - } - - // Contact Daemon - // We contact even if they don't have any daemon permissions to overwrite - // if they did have them previously. - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => $daemonPermissions, - ], - ], - ]); - - DB::commit(); - - return $subuser; - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - - return false; - } - - /** - * Revokes a users permissions on a server. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $subuser = Subuser::with('server.node')->findOrFail($id); - $server = $subuser->server; - - DB::beginTransaction(); - - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => [], - ], - ], - ]); - - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - $subuser->delete(); - DB::commit(); - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to delete this subuser.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates permissions for a given subuser. - * - * @param int $id - * @param array $data - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $validator = Validator::make($data, [ - 'permissions' => 'required|array', - 'user' => 'required|exists:users,id', - 'server' => 'required|exists:servers,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->all())); - } - - $subuser = Subuser::with('server.node')->findOrFail($id); - $server = $subuser->server; - - DB::beginTransaction(); - - try { - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - - $perms = Permission::listPermissions(true); - $daemonPermissions = $this->coreDaemonPermissions; - - foreach ($data['permissions'] as $permission) { - if (array_key_exists($permission, $perms)) { - // Build the daemon permissions array for sending. - if (! is_null($perms[$permission])) { - array_push($daemonPermissions, $perms[$permission]); - } - Permission::create([ - 'subuser_id' => $subuser->id, - 'permission' => $permission, - ]); - } - } - - // Contact Daemon - // We contact even if they don't have any daemon permissions to overwrite - // if they did have them previously. - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => $daemonPermissions, - ], - ], - ]); - - DB::commit(); - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to update permissions.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } -} diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php deleted file mode 100644 index 24290e253..000000000 --- a/app/Repositories/TaskRepository.php +++ /dev/null @@ -1,163 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use Cron; -use Validator; -use Pterodactyl\Models\Task; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class TaskRepository -{ - /** - * The default values to use for new tasks. - * - * @var array - */ - protected $defaults = [ - 'year' => '*', - 'day_of_week' => '*', - 'month' => '*', - 'day_of_month' => '*', - 'hour' => '*', - 'minute' => '*/30', - ]; - - /** - * Task action types. - * - * @var array - */ - protected $actions = [ - 'command', - 'power', - ]; - - /** - * Deletes a given task. - * - * @param int $id - * @return bool - */ - public function delete($id) - { - $task = Task::findOrFail($id); - $task->delete(); - } - - /** - * Toggles a task active or inactive. - * - * @param int $id - * @return bool - */ - public function toggle($id) - { - $task = Task::findOrFail($id); - - $task->active = ! $task->active; - $task->queued = false; - $task->save(); - - return $task->active; - } - - /** - * Create a new scheduled task for a given server. - * - * @param int $server - * @param int $user - * @param array $data - * @return \Pterodactyl\Models\Task - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($server, $user, $data) - { - $server = Server::findOrFail($server); - $user = User::findOrFail($user); - - $validator = Validator::make($data, [ - 'action' => 'string|required', - 'data' => 'string|required', - 'year' => 'string|sometimes', - 'day_of_week' => 'string|sometimes', - 'month' => 'string|sometimes', - 'day_of_month' => 'string|sometimes', - 'hour' => 'string|sometimes', - 'minute' => 'string|sometimes', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (! in_array($data['action'], $this->actions)) { - throw new DisplayException('The action provided is not valid.'); - } - - $cron = $this->defaults; - foreach ($this->defaults as $setting => $value) { - if (array_key_exists($setting, $data) && ! is_null($data[$setting]) && $data[$setting] !== '') { - $cron[$setting] = $data[$setting]; - } - } - - // Check that is this a valid Cron Entry - try { - $buildCron = Cron::factory(sprintf('%s %s %s %s %s %s', - $cron['minute'], - $cron['hour'], - $cron['day_of_month'], - $cron['month'], - $cron['day_of_week'], - $cron['year'] - )); - } catch (\Exception $ex) { - throw $ex; - } - - return Task::create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'active' => 1, - 'action' => $data['action'], - 'data' => $data['data'], - 'queued' => 0, - 'year' => $cron['year'], - 'day_of_week' => $cron['day_of_week'], - 'month' => $cron['month'], - 'day_of_month' => $cron['day_of_month'], - 'hour' => $cron['hour'], - 'minute' => $cron['minute'], - 'last_run' => null, - 'next_run' => $buildCron->getNextRunDate(), - ]); - } -} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php deleted file mode 100644 index 1608afe9c..000000000 --- a/app/Repositories/UserRepository.php +++ /dev/null @@ -1,182 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Auth; -use Hash; -use Settings; -use Validator; -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class UserRepository -{ - /** - * Creates a user on the panel. Returns the created user's ID. - * - * @param array $data - * @return \Pterodactyl\Models\User - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'email' => 'required|email|unique:users,email', - 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, - 'name_first' => 'required|string|between:1,255', - 'name_last' => 'required|string|between:1,255', - 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, - 'root_admin' => 'required|boolean', - 'custom_id' => 'sometimes|nullable|unique:users,id', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $user = new Models\User; - $uuid = new UuidService; - - // Support for API Services - if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { - $user->id = $token; - } - - // UUIDs are not mass-fillable. - $user->uuid = $uuid->generate('users', 'uuid'); - - $user->fill([ - 'email' => $data['email'], - 'username' => $data['username'], - 'name_first' => $data['name_first'], - 'name_last' => $data['name_last'], - 'password' => (empty($data['password'])) ? 'unset' : Hash::make($data['password']), - 'root_admin' => $data['root_admin'], - 'language' => Settings::get('default_language', 'en'), - ]); - $user->save(); - - DB::commit(); - - return $user; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates a user on the panel. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\User - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $user = Models\User::findOrFail($id); - - $validator = Validator::make($data, [ - 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, - 'name_first' => 'sometimes|required|string|between:1,255', - 'name_last' => 'sometimes|required|string|between:1,255', - 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, - 'root_admin' => 'sometimes|required|boolean', - 'language' => 'sometimes|required|string|min:1|max:5', - 'use_totp' => 'sometimes|required|boolean', - 'totp_secret' => 'sometimes|required|size:16', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // The password and root_admin fields are not mass assignable. - if (! empty($data['password'])) { - $data['password'] = Hash::make($data['password']); - } else { - unset($data['password']); - } - - $user->fill($data)->save(); - - return $user; - } - - /** - * Deletes a user on the panel. - * - * @param int $id - * @return void - * @todo Move user self-deletion checking to the controller, rather than the repository. - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $user = Models\User::findOrFail($id); - - if (Models\Server::where('owner_id', $id)->count() > 0) { - throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); - } - - if (! is_null(Auth::user()) && (int) Auth::user()->id === (int) $id) { - throw new DisplayException('Cannot delete your own account.'); - } - - DB::beginTransaction(); - - try { - foreach (Models\Subuser::with('permissions')->where('user_id', $id)->get() as &$subuser) { - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - - $subuser->delete(); - } - - $user->delete(); - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } -} diff --git a/app/Repositories/VariableRepository.php b/app/Repositories/VariableRepository.php deleted file mode 100644 index 1aded8293..000000000 --- a/app/Repositories/VariableRepository.php +++ /dev/null @@ -1,170 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class VariableRepository -{ - /** - * Create a new service variable. - * - * @param int $option - * @param array $data - * @return \Pterodactyl\Models\ServiceVariable - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($option, array $data) - { - $option = ServiceOption::select('id')->findOrFail($option); - - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'sometimes|nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'default_value' => 'string', - 'options' => 'sometimes|required|array', - 'rules' => 'bail|required|string', - ]); - - // Ensure the default value is allowed by the rules provided. - $validator->sometimes('default_value', $data['rules'] ?? null, function ($input) { - return $input->default_value; - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { - throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); - } - - $search = ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id); - if ($search->first()) { - throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); - } - - if (! isset($data['options']) || ! is_array($data['options'])) { - $data['options'] = []; - } - - $data['option_id'] = $option->id; - $data['user_viewable'] = (in_array('user_viewable', $data['options'])); - $data['user_editable'] = (in_array('user_editable', $data['options'])); - - // Remove field that isn't used. - unset($data['options']); - - return ServiceVariable::create($data); - } - - /** - * Deletes a specified option variable as well as all server - * variables currently assigned. - * - * @param int $id - * @return void - */ - public function delete($id) - { - $variable = ServiceVariable::with('serverVariable')->findOrFail($id); - - DB::transaction(function () use ($variable) { - foreach ($variable->serverVariable as $v) { - $v->delete(); - } - - $variable->delete(); - }); - } - - /** - * Updates a given service variable. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceVariable - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $variable = ServiceVariable::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'rules' => 'bail|required|string', - 'options' => 'sometimes|required|array', - ]); - - // Ensure the default value is allowed by the rules provided. - $rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules; - $validator->sometimes('default_value', $rules, function ($input) { - return $input->default_value; - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['env_variable'])) { - if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { - throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); - } - - $search = ServiceVariable::where('env_variable', $data['env_variable']) - ->where('option_id', $variable->option_id) - ->where('id', '!=', $variable->id); - if ($search->first()) { - throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); - } - } - - if (! isset($data['options']) || ! is_array($data['options'])) { - $data['options'] = []; - } - - $data['user_viewable'] = (in_array('user_viewable', $data['options'])); - $data['user_editable'] = (in_array('user_editable', $data['options'])); - - // Remove field that isn't used. - unset($data['options']); - - $variable->fill($data)->save(); - - return $variable; - } -} diff --git a/app/Rules/Username.php b/app/Rules/Username.php new file mode 100644 index 000000000..08d9baba0 --- /dev/null +++ b/app/Rules/Username.php @@ -0,0 +1,36 @@ +. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use Log; -use Illuminate\Http\Request; -use Pterodactyl\Models\APILog; - -class APILogService -{ - /** - * Log an API Request. - * - * @param \Illuminate\Http\Request $request - * @param null|string $error - * @param bool $authorized - * @return void - */ - public static function log(Request $request, $error = null, $authorized = false) - { - if ($request->bearerToken() && ! empty($request->bearerToken())) { - list($public, $hashed) = explode('.', $request->bearerToken()); - } else { - $public = null; - } - - try { - $log = APILog::create([ - 'authorized' => $authorized, - 'error' => $error, - 'key' => $public, - 'method' => $request->method(), - 'route' => $request->fullUrl(), - 'content' => (empty($request->getContent())) ? null : $request->getContent(), - 'user_agent' => $request->header('User-Agent'), - 'request_ip' => $request->ip(), - ]); - $log->save(); - } catch (\Exception $ex) { - // Simply log it and move on. - Log::error($ex); - } - } -} diff --git a/app/Services/Acl/Api/AdminAcl.php b/app/Services/Acl/Api/AdminAcl.php new file mode 100644 index 000000000..54bd594fd --- /dev/null +++ b/app/Services/Acl/Api/AdminAcl.php @@ -0,0 +1,82 @@ +getConstants())->filter(function ($value, $key) { + return substr($key, 0, 9) === 'RESOURCE_'; + })->values()->toArray(); + } +} diff --git a/app/Services/Allocations/AllocationDeletionService.php b/app/Services/Allocations/AllocationDeletionService.php new file mode 100644 index 000000000..5e81a1d2f --- /dev/null +++ b/app/Services/Allocations/AllocationDeletionService.php @@ -0,0 +1,43 @@ +repository = $repository; + } + + /** + * Delete an allocation from the database only if it does not have a server + * that is actively attached to it. + * + * @param \Pterodactyl\Models\Allocation $allocation + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException + */ + public function handle(Allocation $allocation) + { + if (! is_null($allocation->server_id)) { + throw new ServerUsingAllocationException(trans('exceptions.allocations.server_using')); + } + + return $this->repository->delete($allocation->id); + } +} diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php new file mode 100644 index 000000000..10d58ef40 --- /dev/null +++ b/app/Services/Allocations/AssignmentService.php @@ -0,0 +1,110 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Allocations; + +use IPTools\Network; +use Pterodactyl\Models\Node; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; + +class AssignmentService +{ + const CIDR_MAX_BITS = 27; + const CIDR_MIN_BITS = 32; + const PORT_RANGE_LIMIT = 1000; + const PORT_RANGE_REGEX = '/^(\d{1,5})-(\d{1,5})$/'; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $repository; + + /** + * AssignmentService constructor. + * + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + */ + public function __construct( + AllocationRepositoryInterface $repository, + ConnectionInterface $connection + ) { + $this->connection = $connection; + $this->repository = $repository; + } + + /** + * Insert allocations into the database and link them to a specific node. + * + * @param int|\Pterodactyl\Models\Node $node + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle($node, array $data) + { + if ($node instanceof Node) { + $node = $node->id; + } + + $explode = explode('/', $data['allocation_ip']); + if (count($explode) !== 1) { + if (! ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) { + throw new DisplayException(trans('exceptions.allocations.cidr_out_of_range')); + } + } + + $this->connection->beginTransaction(); + foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { + foreach ($data['allocation_ports'] as $port) { + if (! is_digit($port) && ! preg_match(self::PORT_RANGE_REGEX, $port)) { + throw new DisplayException(trans('exceptions.allocations.invalid_mapping', ['port' => $port])); + } + + $insertData = []; + if (preg_match(self::PORT_RANGE_REGEX, $port, $matches)) { + $block = range($matches[1], $matches[2]); + + if (count($block) > self::PORT_RANGE_LIMIT) { + throw new DisplayException(trans('exceptions.allocations.too_many_ports')); + } + + foreach ($block as $unit) { + $insertData[] = [ + 'node_id' => $node, + 'ip' => $ip->__toString(), + 'port' => (int) $unit, + 'ip_alias' => array_get($data, 'allocation_alias'), + 'server_id' => null, + ]; + } + } else { + $insertData[] = [ + 'node_id' => $node, + 'ip' => $ip->__toString(), + 'port' => (int) $port, + 'ip_alias' => array_get($data, 'allocation_alias'), + 'server_id' => null, + ]; + } + + $this->repository->insertIgnore($insertData); + } + } + + $this->connection->commit(); + } +} diff --git a/app/Services/Allocations/SetDefaultAllocationService.php b/app/Services/Allocations/SetDefaultAllocationService.php new file mode 100644 index 000000000..6e9010316 --- /dev/null +++ b/app/Services/Allocations/SetDefaultAllocationService.php @@ -0,0 +1,110 @@ +connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Update the default allocation for a server only if that allocation is currently + * assigned to the specified server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param int $allocation + * @return \Pterodactyl\Models\Allocation + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException + */ + public function handle($server, int $allocation): Allocation + { + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $allocations = $this->repository->findWhere([['server_id', '=', $server->id]]); + $model = $allocations->filter(function ($model) use ($allocation) { + return $model->id === $allocation; + })->first(); + + if (! $model instanceof Allocation) { + throw new AllocationDoesNotBelongToServerException; + } + + $this->connection->beginTransaction(); + $this->serverRepository->withoutFreshModel()->update($server->id, ['allocation_id' => $model->id]); + + // Update on the daemon. + try { + $this->daemonRepository->setServer($server)->update([ + 'build' => [ + 'default' => [ + 'ip' => $model->ip, + 'port' => $model->port, + ], + 'ports|overwrite' => $allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + ], + ]); + + $this->connection->commit(); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + return $model; + } +} diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php new file mode 100644 index 000000000..4a8efe7b4 --- /dev/null +++ b/app/Services/Api/KeyCreationService.php @@ -0,0 +1,79 @@ +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. + * + * @param int $type + * @return \Pterodactyl\Services\Api\KeyCreationService + */ + public function setKeyType(int $type) + { + $this->keyType = $type; + + return $this; + } + + /** + * Create a new API key for the Panel using the permissions passed in the data request. + * This will automatically generate an identifer and an encrypted token that are + * stored in the database. + * + * @param array $data + * @param array $permissions + * @return \Pterodactyl\Models\ApiKey + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data, array $permissions = []): ApiKey + { + $data = array_merge($data, [ + 'key_type' => $this->keyType, + 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), + 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), + ]); + + if ($this->keyType === ApiKey::TYPE_APPLICATION) { + $data = array_merge($data, $permissions); + } + + $instance = $this->repository->create($data, true, true); + + return $instance; + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyCreationService.php b/app/Services/DaemonKeys/DaemonKeyCreationService.php new file mode 100644 index 000000000..23aa1c0aa --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyCreationService.php @@ -0,0 +1,87 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Carbon\Carbon; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyCreationService +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * DaemonKeyCreationService constructor. + * + * @param \Carbon\Carbon $carbon + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + */ + public function __construct( + Carbon $carbon, + ConfigRepository $config, + DaemonKeyRepositoryInterface $repository + ) { + $this->carbon = $carbon; + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new daemon key to be used when connecting to a daemon. + * + * @param int $server + * @param int $user + * @return string + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(int $server, int $user) + { + $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); + + $this->repository->withoutFreshModel()->create([ + 'user_id' => $user, + 'server_id' => $server, + 'secret' => $secret, + 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), + ]); + + return $secret; + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyDeletionService.php b/app/Services/DaemonKeys/DaemonKeyDeletionService.php new file mode 100644 index 000000000..553258ce7 --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyDeletionService.php @@ -0,0 +1,124 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Illuminate\Log\Writer; +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class DaemonKeyDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DaemonKeyDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonKeyRepositoryInterface $repository, + DaemonServerRepositoryInterface $daemonRepository, + ServerRepositoryInterface $serverRepository, + Writer $writer + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->writer = $writer; + } + + /** + * @param \Pterodactyl\Models\Server|int $server + * @param int $user + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server, $user) + { + Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.'); + + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $this->connection->beginTransaction(); + $key = $this->repository->findFirstWhere([ + ['user_id', '=', $user], + ['server_id', '=', $server->id], + ]); + + $this->repository->delete($key->id); + + try { + $this->daemonRepository->setServer($server)->revokeAccessKey($key->secret); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->connection->rollBack(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + $this->connection->commit(); + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyProviderService.php b/app/Services/DaemonKeys/DaemonKeyProviderService.php new file mode 100644 index 000000000..ab19329fa --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyProviderService.php @@ -0,0 +1,104 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Carbon\Carbon; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyProviderService +{ + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + */ + private $keyCreationService; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService + */ + private $keyUpdateService; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + private $repository; + + /** + * GetDaemonKeyService constructor. + * + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService + */ + public function __construct( + DaemonKeyCreationService $keyCreationService, + DaemonKeyRepositoryInterface $repository, + DaemonKeyUpdateService $keyUpdateService + ) { + $this->keyCreationService = $keyCreationService; + $this->keyUpdateService = $keyUpdateService; + $this->repository = $repository; + } + + /** + * Get the access key for a user on a specific server. + * + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @param bool $updateIfExpired + * @return string + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server, User $user, $updateIfExpired = true): string + { + try { + $key = $this->repository->findFirstWhere([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ]); + } catch (RecordNotFoundException $exception) { + // If key doesn't exist but we are an admin or the server owner, + // create it. + if ($user->root_admin || $user->id === $server->owner_id) { + return $this->keyCreationService->handle($server->id, $user->id); + } + + // If they aren't the admin or owner of the server, they shouldn't get access. + // Subusers should always have an entry created when they are, so if there is + // no record, it should fail. + throw $exception; + } + + if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) { + return $key->secret; + } + + return $this->keyUpdateService->handle($key->id); + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyUpdateService.php b/app/Services/DaemonKeys/DaemonKeyUpdateService.php new file mode 100644 index 000000000..750d833d9 --- /dev/null +++ b/app/Services/DaemonKeys/DaemonKeyUpdateService.php @@ -0,0 +1,88 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\DaemonKeys; + +use Carbon\Carbon; +use Webmozart\Assert\Assert; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyUpdateService +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + protected $repository; + + /** + * DaemonKeyUpdateService constructor. + * + * @param \Carbon\Carbon $carbon + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository + */ + public function __construct( + Carbon $carbon, + ConfigRepository $config, + DaemonKeyRepositoryInterface $repository + ) { + $this->carbon = $carbon; + $this->config = $config; + $this->repository = $repository; + } + + /** + * Update a daemon key to expire the previous one. + * + * @param int $key + * @return string + * + * @throws \RuntimeException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($key) + { + Assert::integerish($key, 'First argument passed to handle must be an integer, received %s.'); + + $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); + $this->repository->withoutFreshModel()->update($key, [ + 'secret' => $secret, + 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), + ]); + + return $secret; + } +} diff --git a/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php b/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php new file mode 100644 index 000000000..7059be88e --- /dev/null +++ b/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php @@ -0,0 +1,89 @@ +daemonRepository = $daemonRepository; + $this->repository = $repository; + } + + /** + * Grab all of the keys that exist for a single user and delete them from all + * daemon's that they are assigned to. If connection fails, this function will + * return an error. + * + * @param \Pterodactyl\Models\User $user + * @param bool $ignoreConnectionErrors + */ + public function handle(User $user, bool $ignoreConnectionErrors = false) + { + $keys = $this->repository->getKeysForRevocation($user); + + $keys->groupBy('node.id')->each(function ($group, $nodeId) use ($ignoreConnectionErrors) { + try { + $this->daemonRepository->setNode(collect($group)->first()->getRelation('node'))->revokeAccessKey(collect($group)->pluck('secret')->toArray()); + } catch (RequestException $exception) { + if (! $ignoreConnectionErrors) { + throw new DaemonConnectionException($exception); + } + + $this->setConnectionException($nodeId, $exception); + } + + $this->repository->deleteKeys(collect($group)->pluck('id')->toArray()); + }); + } + + /** + * Returns an array of exceptions that were returned by the handle function. + * + * @return RequestException[] + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * Add an exception for a node to the array. + * + * @param int $node + * @param \GuzzleHttp\Exception\RequestException $exception + */ + protected function setConnectionException(int $node, RequestException $exception) + { + $this->exceptions[$node] = $exception; + } +} diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php new file mode 100644 index 000000000..95182a288 --- /dev/null +++ b/app/Services/Databases/DatabaseManagementService.php @@ -0,0 +1,134 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Databases; + +use Pterodactyl\Models\Database; +use Illuminate\Database\DatabaseManager; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; + +class DatabaseManagementService +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Extensions\DynamicDatabaseConnection + */ + protected $dynamic; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + DatabaseManager $database, + DynamicDatabaseConnection $dynamic, + DatabaseRepositoryInterface $repository, + Encrypter $encrypter + ) { + $this->database = $database; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Create a new database that is linked to a specific host. + * + * @param int $server + * @param array $data + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create($server, array $data) + { + $data['server_id'] = $server; + $data['database'] = sprintf('d%d_%s', $server, $data['database']); + $data['username'] = sprintf('u%d_%s', $server, str_random(10)); + $data['password'] = $this->encrypter->encrypt(str_random(16)); + + $this->database->beginTransaction(); + try { + $database = $this->repository->createIfNotExists($data); + $this->dynamic->set('dynamic', $data['database_host_id']); + + $this->repository->createDatabase($database->database); + $this->repository->createUser( + $database->username, + $database->remote, + $this->encrypter->decrypt($database->password) + ); + $this->repository->assignUserToDatabase( + $database->database, + $database->username, + $database->remote + ); + $this->repository->flush(); + + $this->database->commit(); + } catch (\Exception $ex) { + try { + if (isset($database) && $database instanceof Database) { + $this->repository->dropDatabase($database->database); + $this->repository->dropUser($database->username, $database->remote); + $this->repository->flush(); + } + } catch (\Exception $exTwo) { + // ignore an exception + } + + $this->database->rollBack(); + throw $ex; + } + + return $database; + } + + /** + * Delete a database from the given host server. + * + * @param int $id + * @return bool|null + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function delete($id) + { + $database = $this->repository->find($id); + $this->dynamic->set('dynamic', $database->database_host_id); + + $this->repository->dropDatabase($database->database); + $this->repository->dropUser($database->username, $database->remote); + $this->repository->flush(); + + return $this->repository->delete($id); + } +} diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php new file mode 100644 index 000000000..8f8a9582d --- /dev/null +++ b/app/Services/Databases/DatabasePasswordService.php @@ -0,0 +1,86 @@ +connection = $connection; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Updates a password for a given database. + * + * @param \Pterodactyl\Models\Database|int $database + * @param string $password + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($database, string $password): bool + { + if (! $database instanceof Database) { + $database = $this->repository->find($database); + } + + $this->dynamic->set('dynamic', $database->database_host_id); + $this->connection->beginTransaction(); + + $updated = $this->repository->withoutFreshModel()->update($database->id, [ + 'password' => $this->encrypter->encrypt($password), + ]); + + $this->repository->dropUser($database->username, $database->remote); + $this->repository->createUser($database->username, $database->remote, $password); + $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); + $this->repository->flush(); + + unset($password); + $this->connection->commit(); + + return $updated; + } +} diff --git a/app/Services/Databases/Hosts/HostCreationService.php b/app/Services/Databases/Hosts/HostCreationService.php new file mode 100644 index 000000000..15b32ea04 --- /dev/null +++ b/app/Services/Databases/Hosts/HostCreationService.php @@ -0,0 +1,92 @@ +connection = $connection; + $this->databaseManager = $databaseManager; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Create a new database host on the Panel. + * + * @param array $data + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(array $data): DatabaseHost + { + $this->connection->beginTransaction(); + + $host = $this->repository->create([ + 'password' => $this->encrypter->encrypt(array_get($data, 'password')), + 'name' => array_get($data, 'name'), + 'host' => array_get($data, 'host'), + 'port' => array_get($data, 'port'), + 'username' => array_get($data, 'username'), + 'max_databases' => null, + 'node_id' => array_get($data, 'node_id'), + ]); + + // Confirm access using the provided credentials before saving data. + $this->dynamic->set('dynamic', $host); + $this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual'); + $this->connection->commit(); + + return $host; + } +} diff --git a/app/Services/Databases/Hosts/HostDeletionService.php b/app/Services/Databases/Hosts/HostDeletionService.php new file mode 100644 index 000000000..b69c8dcf9 --- /dev/null +++ b/app/Services/Databases/Hosts/HostDeletionService.php @@ -0,0 +1,53 @@ +databaseRepository = $databaseRepository; + $this->repository = $repository; + } + + /** + * Delete a specified host from the Panel if no databases are + * attached to it. + * + * @param int $host + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle(int $host): int + { + $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $host]]); + if ($count > 0) { + throw new HasActiveServersException(trans('exceptions.databases.delete_has_databases')); + } + + return $this->repository->delete($host); + } +} diff --git a/app/Services/Databases/Hosts/HostUpdateService.php b/app/Services/Databases/Hosts/HostUpdateService.php new file mode 100644 index 000000000..5f4b19b31 --- /dev/null +++ b/app/Services/Databases/Hosts/HostUpdateService.php @@ -0,0 +1,96 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Databases\Hosts; + +use Pterodactyl\Models\DatabaseHost; +use Illuminate\Database\DatabaseManager; +use Illuminate\Database\ConnectionInterface; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +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. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\DatabaseManager $databaseManager + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + ConnectionInterface $connection, + DatabaseManager $databaseManager, + DatabaseHostRepositoryInterface $repository, + DynamicDatabaseConnection $dynamic, + Encrypter $encrypter + ) { + $this->connection = $connection; + $this->databaseManager = $databaseManager; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Update a database host and persist to the database. + * + * @param int $hostId + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $hostId, array $data): DatabaseHost + { + if (! empty(array_get($data, 'password'))) { + $data['password'] = $this->encrypter->encrypt($data['password']); + } else { + unset($data['password']); + } + + $this->connection->beginTransaction(); + $host = $this->repository->update($hostId, $data); + + $this->dynamic->set('dynamic', $host); + $this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual'); + $this->connection->commit(); + + return $host; + } +} diff --git a/app/Services/Deployment/AllocationSelectionService.php b/app/Services/Deployment/AllocationSelectionService.php new file mode 100644 index 000000000..633ba1f5e --- /dev/null +++ b/app/Services/Deployment/AllocationSelectionService.php @@ -0,0 +1,123 @@ +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. + * + * @param bool $dedicated + * @return $this + */ + public function setDedicated(bool $dedicated) + { + $this->dedicated = $dedicated; + + return $this; + } + + /** + * A list of node IDs that should be used when selecting an allocation. If empty, all + * nodes will be used to filter with. + * + * @param array $nodes + * @return $this + */ + public function setNodes(array $nodes) + { + $this->nodes = $nodes; + + return $this; + } + + /** + * An array of individual ports or port ranges to use when selecting an allocation. If + * empty, all ports will be considered when finding an allocation. If set, only ports appearing + * in the array or range will be used. + * + * @param array $ports + * @return $this + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function setPorts(array $ports) + { + $stored = []; + foreach ($ports as $port) { + if (is_digit($port)) { + $stored[] = $port; + } + + // Ranges are stored in the ports array as an array which can be + // better processed in the repository. + if (preg_match(AssignmentService::PORT_RANGE_REGEX, $port, $matches)) { + if (abs($matches[2] - $matches[1]) > AssignmentService::PORT_RANGE_LIMIT) { + throw new DisplayException(trans('exceptions.allocations.too_many_ports')); + } + + $stored[] = [$matches[1], $matches[2]]; + } + } + + $this->ports = $stored; + + return $this; + } + + /** + * Return a single allocation that should be used as the default allocation for a server. + * + * @return \Pterodactyl\Models\Allocation + * + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + */ + public function handle(): Allocation + { + $allocation = $this->repository->getRandomAllocation($this->nodes, $this->ports, $this->dedicated); + + if (is_null($allocation)) { + throw new NoViableAllocationException(trans('exceptions.deployment.no_viable_allocations')); + } + + return $allocation; + } +} diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php new file mode 100644 index 000000000..973d7fc71 --- /dev/null +++ b/app/Services/Deployment/FindViableNodesService.php @@ -0,0 +1,121 @@ +repository = $repository; + } + + /** + * Set the locations that should be searched through to locate available nodes. + * + * @param array $locations + * @return $this + */ + public function setLocations(array $locations): self + { + $this->locations = $locations; + + return $this; + } + + /** + * 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. + * + * @param int $disk + * @return $this + */ + public function setDisk(int $disk): self + { + $this->disk = $disk; + + return $this; + } + + /** + * 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. + * + * @param int $memory + * @return $this + */ + public function setMemory(int $memory): self + { + $this->memory = $memory; + + return $this; + } + + /** + * Returns an array of nodes that meet the provided requirements and can then + * be passed to the AllocationSelectionService to return a single allocation. + * + * This functionality is used for automatic deployments of servers and will + * attempt to find all nodes in the defined locations that meet the disk and + * memory availability requirements. Any nodes not meeting those requirements + * are tossed out, as are any nodes marked as non-public, meaning automatic + * deployments should not be done aganist them. + * + * @return int[] + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + */ + public function handle(): array + { + Assert::integer($this->disk, 'Calls to ' . __METHOD__ . ' must have the disk space set as an integer, received %s'); + Assert::integer($this->memory, 'Calls to ' . __METHOD__ . ' must have the memory usage set as an integer, received %s'); + + $nodes = $this->repository->getNodesWithResourceUse($this->locations, $this->disk, $this->memory); + $viable = []; + + foreach ($nodes as $node) { + $memoryLimit = $node->memory * (1 + ($node->memory_overallocate / 100)); + $diskLimit = $node->disk * (1 + ($node->disk_overallocate / 100)); + + if (($node->sum_memory + $this->memory) > $memoryLimit || ($node->sum_disk + $this->disk) > $diskLimit) { + continue; + } + + $viable[] = $node->id; + } + + if (empty($viable)) { + throw new NoViableNodeException(trans('exceptions.deployment.no_viable_nodes')); + } + + return $viable; + } +} diff --git a/app/Services/DeploymentService.php b/app/Services/DeploymentService.php deleted file mode 100644 index f25c04e93..000000000 --- a/app/Services/DeploymentService.php +++ /dev/null @@ -1,258 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use DB; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Location; -use Pterodactyl\Exceptions\AutoDeploymentException; - -class DeploymentService -{ - /** - * Eloquent model representing the allocation to use. - * - * @var \Pterodactyl\Models\Allocation - */ - protected $allocation; - - /** - * Amount of disk to be used by the server. - * - * @var int - */ - protected $disk; - - /** - * Amount of memory to be used by the sever. - * - * @var int - */ - protected $memory; - - /** - * Eloquent model representing the location to use. - * - * @var \Pterodactyl\Models\Location - */ - protected $location; - - /** - * Eloquent model representing the node to use. - * - * @var \Pterodactyl\Models\Node - */ - protected $node; - - /** - * Set the location to use when auto-deploying. - * - * @param int|\Pterodactyl\Models\Location $location - * @return void - */ - public function setLocation($location) - { - $this->location = ($location instanceof Location) ? $location : Location::with('nodes')->findOrFail($location); - if (! $this->location->relationLoaded('nodes')) { - $this->location->load('nodes'); - } - - if (count($this->location->nodes) < 1) { - throw new AutoDeploymentException('The location provided does not contain any nodes and cannot be used.'); - } - - return $this; - } - - /** - * Set the node to use when auto-deploying. - * - * @param int|\Pterodactyl\Models\Node $node - * @return void - */ - public function setNode($node) - { - $this->node = ($node instanceof Node) ? $node : Node::findOrFail($node); - if (! $this->node->relationLoaded('allocations')) { - $this->node->load('allocations'); - } - - $this->setLocation($this->node->location); - - return $this; - } - - /** - * Set the amount of disk space to be used by the new server. - * - * @param int $disk - * @return void - */ - public function setDisk(int $disk) - { - $this->disk = $disk; - - return $this; - } - - /** - * Set the amount of memory to be used by the new server. - * - * @param int $memory - * @return void - */ - public function setMemory(int $memory) - { - $this->memory = $memory; - - return $this; - } - - /** - * Return a random location model. - * - * @param array $exclude - * @return void; - */ - protected function findLocation(array $exclude = []) - { - $location = Location::with('nodes')->whereNotIn('id', $exclude)->inRandomOrder()->first(); - - if (! $location) { - throw new AutoDeploymentException('Unable to locate a suitable location to select a node from.'); - } - - if (count($location->nodes) < 1) { - return $this->findLocation(array_merge($exclude, [$location->id])); - } - - $this->setLocation($location); - } - - /** - * Return a model instance of a random node. - * - * @return void; - */ - protected function findNode(array $exclude = []) - { - if (! $this->location) { - $this->setLocation($this->findLocation()); - } - - $select = $this->location->nodes->whereNotIn('id', $exclude); - if (count($select) < 1) { - throw new AutoDeploymentException('Unable to find a suitable node within the assigned location with enough space.'); - } - - // Check usage, select new node if necessary - $this->setNode($select->random()); - if (! $this->checkNodeUsage()) { - return $this->findNode(array_merge($exclude, [$this->node()->id])); - } - } - - /** - * Checks that a node's allocation limits will not be passed - * with the assigned limits. - * - * @return bool - */ - protected function checkNodeUsage() - { - if (! $this->disk && ! $this->memory) { - return true; - } - - $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $this->node()->id)->first(); - - if ($this->memory) { - $limit = ($this->node()->memory * (1 + ($this->node()->memory_overallocate / 100))); - - if (($totals->memory + $this->memory) > $limit) { - return false; - } - } - - if ($this->disk) { - $limit = ($this->node()->disk * (1 + ($this->node()->disk_overallocate / 100))); - - if (($totals->disk + $this->disk) > $limit) { - return false; - } - } - - return true; - } - - /** - * Return the assigned node for this auto-deployment. - * - * @return \Pterodactyl\Models\Node - */ - public function node() - { - return $this->node; - } - - /** - * Return the assigned location for this auto-deployment. - * - * @return \Pterodactyl\Models\Location - */ - public function location() - { - return $this->location; - } - - /** - * Return the assigned location for this auto-deployment. - * - * @return \Pterodactyl\Models\Allocation - */ - public function allocation() - { - return $this->allocation; - } - - /** - * Select and return the node to be used by the auto-deployment system. - * - * @return void - */ - public function select() - { - if (! $this->node) { - $this->findNode(); - } - - // Set the Allocation - $this->allocation = $this->node()->allocations->where('server_id', null)->random(); - if (! $this->allocation) { - throw new AutoDeploymentException('Unable to find a suitable allocation to assign to this server.'); - } - } -} diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php new file mode 100644 index 000000000..a73e3f6a8 --- /dev/null +++ b/app/Services/Eggs/EggConfigurationService.php @@ -0,0 +1,54 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggConfigurationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggConfigurationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Return an Egg file to be used by the Daemon. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($egg): array + { + if (! $egg instanceof Egg) { + $egg = $this->repository->getWithCopyAttributes($egg); + } + + return [ + 'startup' => json_decode($egg->inherit_config_startup), + 'stop' => $egg->inherit_config_stop, + 'configs' => json_decode($egg->inherit_config_files), + 'log' => json_decode($egg->inherit_config_logs), + 'query' => 'none', + ]; + } +} diff --git a/app/Services/Eggs/EggCreationService.php b/app/Services/Eggs/EggCreationService.php new file mode 100644 index 000000000..aaf9823f1 --- /dev/null +++ b/app/Services/Eggs/EggCreationService.php @@ -0,0 +1,71 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +// When a mommy and a daddy pterodactyl really like eachother... +class EggCreationService +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggCreationService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(ConfigRepository $config, EggRepositoryInterface $repository) + { + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new service option and assign it to the given service. + * + * @param array $data + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function handle(array $data): Egg + { + $data['config_from'] = array_get($data, 'config_from'); + if (! is_null($data['config_from'])) { + $results = $this->repository->findCountWhere([ + ['nest_id', '=', array_get($data, 'nest_id')], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.nest.egg.must_be_child')); + } + } + + return $this->repository->create(array_merge($data, [ + 'uuid' => Uuid::uuid4()->toString(), + 'author' => $this->config->get('pterodactyl.service.author'), + ]), true, true); + } +} diff --git a/app/Services/Eggs/EggDeletionService.php b/app/Services/Eggs/EggDeletionService.php new file mode 100644 index 000000000..5179f6a50 --- /dev/null +++ b/app/Services/Eggs/EggDeletionService.php @@ -0,0 +1,66 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\HasChildrenException; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class EggDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * EggDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + EggRepositoryInterface $repository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Delete an Egg from the database if it has no active servers attached to it. + * + * @param int $egg + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException + */ + public function handle(int $egg): int + { + $servers = $this->serverRepository->findCountWhere([['egg_id', '=', $egg]]); + if ($servers > 0) { + throw new HasActiveServersException(trans('exceptions.nest.egg.delete_has_servers')); + } + + $children = $this->repository->findCountWhere([['config_from', '=', $egg]]); + if ($children > 0) { + throw new HasChildrenException(trans('exceptions.nest.egg.has_children')); + } + + return $this->repository->delete($egg); + } +} diff --git a/app/Services/Eggs/EggUpdateService.php b/app/Services/Eggs/EggUpdateService.php new file mode 100644 index 000000000..14d655178 --- /dev/null +++ b/app/Services/Eggs/EggUpdateService.php @@ -0,0 +1,62 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a service option. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function handle($egg, array $data) + { + if (! $egg instanceof Egg) { + $egg = $this->repository->find($egg); + } + + if (! is_null(array_get($data, 'config_from'))) { + $results = $this->repository->findCountWhere([ + ['nest_id', '=', $egg->nest_id], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.nest.egg.must_be_child')); + } + } + + $this->repository->withoutFreshModel()->update($egg->id, $data); + } +} diff --git a/app/Services/Eggs/Scripts/InstallScriptService.php b/app/Services/Eggs/Scripts/InstallScriptService.php new file mode 100644 index 000000000..0d9e66bcc --- /dev/null +++ b/app/Services/Eggs/Scripts/InstallScriptService.php @@ -0,0 +1,63 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Scripts; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException; + +class InstallScriptService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * InstallScriptService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Modify the install script for a given Egg. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException + */ + public function handle($egg, array $data) + { + if (! $egg instanceof Egg) { + $egg = $this->repository->find($egg); + } + + if (! is_null(array_get($data, 'copy_script_from'))) { + if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) { + throw new InvalidCopyFromException(trans('exceptions.nest.egg.invalid_copy_id')); + } + } + + $this->repository->withoutFreshModel()->update($egg->id, [ + 'script_install' => array_get($data, 'script_install'), + 'script_is_privileged' => array_get($data, 'script_is_privileged', 1), + 'script_entry' => array_get($data, 'script_entry'), + 'script_container' => array_get($data, 'script_container'), + 'copy_script_from' => array_get($data, 'copy_script_from'), + ]); + } +} diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php new file mode 100644 index 000000000..25a7131fa --- /dev/null +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -0,0 +1,77 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Sharing; + +use Carbon\Carbon; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggExporterService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggExporterService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Return a JSON representation of an egg and its variables. + * + * @param int $egg + * @return string + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $egg): string + { + $egg = $this->repository->getWithExportAttributes($egg); + + $struct = [ + '_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO', + 'meta' => [ + 'version' => 'PTDL_v1', + ], + 'exported_at' => Carbon::now()->toIso8601String(), + 'name' => $egg->name, + 'author' => $egg->author, + 'description' => $egg->description, + 'image' => $egg->docker_image, + 'startup' => $egg->startup, + 'config' => [ + 'files' => $egg->inherit_config_files, + 'startup' => $egg->inherit_config_startup, + 'logs' => $egg->inherit_config_logs, + 'stop' => $egg->inherit_config_stop, + ], + 'scripts' => [ + 'installation' => [ + 'script' => $egg->copy_script_install, + 'container' => $egg->copy_script_container, + 'entrypoint' => $egg->copy_script_entry, + ], + ], + 'variables' => $egg->variables->transform(function ($item) { + return collect($item->toArray())->except([ + 'id', 'egg_id', 'created_at', 'updated_at', + ])->toArray(); + }), + ]; + + return json_encode($struct, JSON_PRETTY_PRINT); + } +} diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php new file mode 100644 index 000000000..d4e048f9a --- /dev/null +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -0,0 +1,124 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Sharing; + +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Egg; +use Illuminate\Http\UploadedFile; +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; + +class EggImporterService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface + */ + protected $eggVariableRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $nestRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggImporterService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $eggVariableRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository + */ + public function __construct( + ConnectionInterface $connection, + EggRepositoryInterface $repository, + EggVariableRepositoryInterface $eggVariableRepository, + NestRepositoryInterface $nestRepository + ) { + $this->connection = $connection; + $this->eggVariableRepository = $eggVariableRepository; + $this->repository = $repository; + $this->nestRepository = $nestRepository; + } + + /** + * Take an uploaded JSON file and parse it into a new egg. + * + * @param \Illuminate\Http\UploadedFile $file + * @param int $nest + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function handle(UploadedFile $file, int $nest): Egg + { + if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + } + + $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(), + ])); + } + + if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); + } + + $nest = $this->nestRepository->getWithEggs($nest); + $this->connection->beginTransaction(); + + $egg = $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'nest_id' => $nest->id, + 'author' => object_get($parsed, 'author'), + 'name' => object_get($parsed, 'name'), + 'description' => object_get($parsed, 'description'), + 'docker_image' => 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'), + 'copy_script_from' => null, + ], true, true); + + collect($parsed->variables)->each(function ($variable) use ($egg) { + $this->eggVariableRepository->create(array_merge((array) $variable, [ + 'egg_id' => $egg->id, + ])); + }); + + $this->connection->commit(); + + return $egg; + } +} diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php new file mode 100644 index 000000000..50342667d --- /dev/null +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -0,0 +1,113 @@ +connection = $connection; + $this->repository = $repository; + $this->variableRepository = $variableRepository; + } + + /** + * Update an existing Egg using an uploaded JSON file. + * + * @param int $egg + * @param \Illuminate\Http\UploadedFile $file + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function handle(int $egg, UploadedFile $file) + { + if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + } + + $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(), + ])); + } + + if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); + } + + $this->connection->beginTransaction(); + $this->repository->update($egg, [ + 'author' => object_get($parsed, 'author'), + 'name' => object_get($parsed, 'name'), + 'description' => object_get($parsed, 'description'), + 'docker_image' => 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); + + // Update Existing Variables + collect($parsed->variables)->each(function ($variable) use ($egg) { + $this->variableRepository->withoutFreshModel()->updateOrCreate([ + 'egg_id' => $egg, + 'env_variable' => $variable->env_variable, + ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); + }); + + $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); + $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg]]); + + // 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], + ['env_variable', '=', $variable->env_variable], + ]); + } + }); + + $this->connection->commit(); + } +} diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php new file mode 100644 index 000000000..35fc399a0 --- /dev/null +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -0,0 +1,58 @@ +repository = $repository; + } + + /** + * Create a new variable for a given Egg. + * + * @param int $egg + * @param array $data + * @return \Pterodactyl\Models\EggVariable + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function handle(int $egg, array $data): EggVariable + { + if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { + throw new ReservedVariableNameException(sprintf( + 'Cannot use the protected name %s for this environment variable.', + array_get($data, 'env_variable') + )); + } + + $options = array_get($data, 'options') ?? []; + + return $this->repository->create([ + 'egg_id' => $egg, + '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), + 'rules' => $data['rules'] ?? '', + ]); + } +} diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php new file mode 100644 index 000000000..9960e78c9 --- /dev/null +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -0,0 +1,73 @@ +repository = $repository; + } + + /** + * Update a specific egg variable. + * + * @param \Pterodactyl\Models\EggVariable $variable + * @param array $data + * @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) + { + 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], + ]); + + if ($search > 0) { + throw new DisplayException(trans('exceptions.service.variables.env_not_unique', [ + 'name' => array_get($data, 'env_variable'), + ])); + } + } + + $options = array_get($data, 'options') ?? []; + + return $this->repository->withoutFreshModel()->update($variable->id, [ + '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), + 'rules' => $data['rules'] ?? '', + ]); + } +} diff --git a/app/Services/Helpers/ApiAllowedIpsValidatorService.php b/app/Services/Helpers/ApiAllowedIpsValidatorService.php new file mode 100644 index 000000000..7051cd539 --- /dev/null +++ b/app/Services/Helpers/ApiAllowedIpsValidatorService.php @@ -0,0 +1,8 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php new file mode 100644 index 000000000..0d91bb9aa --- /dev/null +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -0,0 +1,135 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Helpers; + +use stdClass; +use Exception; +use GuzzleHttp\Client; +use Illuminate\Contracts\Cache\Repository as CacheRepository; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; + +class SoftwareVersionService +{ + const VERSION_CACHE_KEY = 'pterodactyl:versions'; + + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + protected $cache; + + /** + * @var \GuzzleHttp\Client + */ + protected $client; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * SoftwareVersionService constructor. + * + * @param \Illuminate\Contracts\Cache\Repository $cache + * @param \GuzzleHttp\Client $client + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct( + CacheRepository $cache, + Client $client, + ConfigRepository $config + ) { + $this->cache = $cache; + $this->client = $client; + $this->config = $config; + + $this->cacheVersionData(); + } + + /** + * Get the latest version of the panel from the CDN servers. + * + * @return string + */ + public function getPanel() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error'); + } + + /** + * Get the latest version of the daemon from the CDN servers. + * + * @return string + */ + public function getDaemon() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error'); + } + + /** + * Get the URL to the discord server. + * + * @return string + */ + public function getDiscord() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord'); + } + + /** + * Determine if the current version of the panel is the latest. + * + * @return bool + */ + public function isLatestPanel() + { + if ($this->config->get('app.version') === 'canary') { + return true; + } + + return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0; + } + + /** + * Determine if a passed daemon version string is the latest. + * + * @param string $version + * @return bool + */ + public function isLatestDaemon($version) + { + if ($version === '0.0.0-canary') { + return true; + } + + return version_compare($version, $this->getDaemon()) >= 0; + } + + /** + * Keeps the versioning cache up-to-date with the latest results from the CDN. + */ + protected function cacheVersionData() + { + $this->cache->remember(self::VERSION_CACHE_KEY, $this->config->get('pterodactyl.cdn.cache_time'), function () { + try { + $response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url')); + + if ($response->getStatusCode() === 200) { + return json_decode($response->getBody()); + } + + throw new CdnVersionFetchingException; + } catch (Exception $exception) { + return new stdClass(); + } + }); + } +} diff --git a/app/Services/Helpers/TemporaryPasswordService.php b/app/Services/Helpers/TemporaryPasswordService.php new file mode 100644 index 000000000..90a65b8c8 --- /dev/null +++ b/app/Services/Helpers/TemporaryPasswordService.php @@ -0,0 +1,69 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Helpers; + +use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Database\ConnectionInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class TemporaryPasswordService +{ + const HMAC_ALGO = 'sha256'; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Illuminate\Contracts\Hashing\Hasher + */ + protected $hasher; + + /** + * TemporaryPasswordService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + */ + public function __construct( + ConfigRepository $config, + ConnectionInterface $connection, + Hasher $hasher + ) { + $this->config = $config; + $this->connection = $connection; + $this->hasher = $hasher; + } + + /** + * Store a password reset token for a specific email address. + * + * @param string $email + * @return string + */ + public function handle($email) + { + $token = hash_hmac(self::HMAC_ALGO, str_random(40), $this->config->get('app.key')); + + $this->connection->table('password_resets')->insert([ + 'email' => $email, + 'token' => $this->hasher->make($token), + ]); + + return $token; + } +} diff --git a/app/Services/Locations/LocationCreationService.php b/app/Services/Locations/LocationCreationService.php new file mode 100644 index 000000000..cd2a97572 --- /dev/null +++ b/app/Services/Locations/LocationCreationService.php @@ -0,0 +1,43 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Locations; + +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * LocationCreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + */ + public function __construct(LocationRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new location. + * + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + return $this->repository->create($data); + } +} diff --git a/app/Services/Locations/LocationDeletionService.php b/app/Services/Locations/LocationDeletionService.php new file mode 100644 index 000000000..5d1495d16 --- /dev/null +++ b/app/Services/Locations/LocationDeletionService.php @@ -0,0 +1,65 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Locations; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Location; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException; + +class LocationDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * LocationDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + */ + public function __construct( + LocationRepositoryInterface $repository, + 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) + { + $location = ($location instanceof Location) ? $location->id : $location; + + Assert::integerish($location, 'First argument passed to handle must be numeric or an instance of ' . Location::class . ', received %s.'); + + $count = $this->nodeRepository->findCountWhere([['location_id', '=', $location]]); + if ($count > 0) { + throw new HasActiveNodesException(trans('exceptions.locations.has_nodes')); + } + + return $this->repository->delete($location); + } +} diff --git a/app/Services/Locations/LocationUpdateService.php b/app/Services/Locations/LocationUpdateService.php new file mode 100644 index 000000000..a245e71b1 --- /dev/null +++ b/app/Services/Locations/LocationUpdateService.php @@ -0,0 +1,48 @@ +. + * + * 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 LocationUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * LocationUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + */ + public function __construct(LocationRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update an existing location. + * + * @param int|\Pterodactyl\Models\Location $location + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($location, array $data) + { + $location = ($location instanceof Location) ? $location->id : $location; + + return $this->repository->update($location, $data); + } +} diff --git a/app/Services/Nests/NestCreationService.php b/app/Services/Nests/NestCreationService.php new file mode 100644 index 000000000..439164485 --- /dev/null +++ b/app/Services/Nests/NestCreationService.php @@ -0,0 +1,51 @@ +config = $config; + $this->repository = $repository; + } + + /** + * Create a new nest on the system. + * + * @param array $data + * @param string|null $author + * @return \Pterodactyl\Models\Nest + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data, string $author = null): Nest + { + return $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'author' => $author ?? $this->config->get('pterodactyl.service.author'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + ], true, true); + } +} diff --git a/app/Services/Nests/NestDeletionService.php b/app/Services/Nests/NestDeletionService.php new file mode 100644 index 000000000..6bdaf8de2 --- /dev/null +++ b/app/Services/Nests/NestDeletionService.php @@ -0,0 +1,59 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nests; + +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class NestDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $repository; + + /** + * NestDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + NestRepositoryInterface $repository + ) { + $this->serverRepository = $serverRepository; + $this->repository = $repository; + } + + /** + * Delete a nest from the system only if there are no servers attached to it. + * + * @param int $nest + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle(int $nest): int + { + $count = $this->serverRepository->findCountWhere([['nest_id', '=', $nest]]); + if ($count > 0) { + throw new HasActiveServersException(trans('exceptions.service.delete_has_servers')); + } + + return $this->repository->delete($nest); + } +} diff --git a/app/Services/Nests/NestUpdateService.php b/app/Services/Nests/NestUpdateService.php new file mode 100644 index 000000000..c3cdcdd5d --- /dev/null +++ b/app/Services/Nests/NestUpdateService.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nests; + +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; + +class NestUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $repository; + + /** + * NestUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + */ + public function __construct(NestRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a nest and prevent changing the author once it is set. + * + * @param int $nest + * @param array $data + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $nest, array $data) + { + if (! is_null(array_get($data, 'author'))) { + unset($data['author']); + } + + $this->repository->withoutFreshModel()->update($nest, $data); + } +} diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php new file mode 100644 index 000000000..889e81a20 --- /dev/null +++ b/app/Services/Nodes/NodeCreationService.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nodes; + +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; + +class NodeCreationService +{ + const DAEMON_SECRET_LENGTH = 36; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + */ + public function __construct(NodeRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new node on the panel. + * + * @param array $data + * @return \Pterodactyl\Models\Node + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + $data['daemonSecret'] = str_random(self::DAEMON_SECRET_LENGTH); + + return $this->repository->create($data); + } +} diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php new file mode 100644 index 000000000..e4d2ed999 --- /dev/null +++ b/app/Services/Nodes/NodeDeletionService.php @@ -0,0 +1,73 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nodes; + +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +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. + * + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Contracts\Translation\Translator $translator + */ + public function __construct( + NodeRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository, + 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) + { + if ($node instanceof Node) { + $node = $node->id; + } + + $servers = $this->serverRepository->setColumns('id')->findCountWhere([['node_id', '=', $node]]); + if ($servers > 0) { + throw new HasActiveServersException($this->translator->trans('exceptions.node.servers_attached')); + } + + return $this->repository->delete($node); + } +} diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php new file mode 100644 index 000000000..ef9349a71 --- /dev/null +++ b/app/Services/Nodes/NodeUpdateService.php @@ -0,0 +1,100 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nodes; + +use Pterodactyl\Models\Node; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Traits\Services\ReturnsUpdatedModels; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException; +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; + +class NodeUpdateService +{ + use ReturnsUpdatedModels; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface + */ + private $configRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + private $repository; + + /** + * UpdateService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface $configurationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + ConfigurationRepositoryInterface $configurationRepository, + NodeRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->configRepository = $configurationRepository; + $this->repository = $repository; + } + + /** + * Update the configuration values for a given node on the machine. + * + * @param \Pterodactyl\Models\Node $node + * @param array $data + * @return \Pterodactyl\Models\Node|mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Node $node, array $data) + { + if (! is_null(array_get($data, 'reset_secret'))) { + $data['daemonSecret'] = str_random(Node::DAEMON_SECRET_LENGTH); + unset($data['reset_secret']); + } + + $this->connection->beginTransaction(); + if ($this->getUpdatedModel()) { + $response = $this->repository->update($node->id, $data); + } else { + $response = $this->repository->withoutFreshModel()->update($node->id, $data); + } + + try { + $this->configRepository->setNode($node)->update(); + $this->connection->commit(); + } catch (RequestException $exception) { + // Failed to connect to the Daemon. Let's go ahead and save the configuration + // and let the user know they'll need to manually update. + if ($exception instanceof ConnectException) { + $this->connection->commit(); + + throw new ConfigurationNotPersistedException(trans('exceptions.node.daemon_off_config_updated')); + } + + throw new DaemonConnectionException($exception); + } + + return $response; + } +} diff --git a/app/Services/Packs/ExportPackService.php b/app/Services/Packs/ExportPackService.php new file mode 100644 index 000000000..24b4db96b --- /dev/null +++ b/app/Services/Packs/ExportPackService.php @@ -0,0 +1,97 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Packs; + +use ZipArchive; +use Pterodactyl\Models\Pack; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; +use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException; + +class ExportPackService +{ + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * ExportPackService constructor. + * + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \ZipArchive $archive + */ + public function __construct( + FilesystemFactory $storage, + PackRepositoryInterface $repository, + ZipArchive $archive + ) { + $this->archive = $archive; + $this->repository = $repository; + $this->storage = $storage; + } + + /** + * Prepare a pack for export. + * + * @param int|\Pterodactyl\Models\Pack $pack + * @param bool $files + * @return string + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException + */ + public function handle($pack, $files = false) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->find($pack); + } + + $json = [ + 'name' => $pack->name, + 'version' => $pack->version, + 'description' => $pack->description, + 'selectable' => $pack->selectable, + 'visible' => $pack->visible, + 'locked' => $pack->locked, + ]; + + $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); + if ($files) { + if (! $this->archive->open($filename, $this->archive::CREATE)) { + throw new ZipArchiveCreationException; + } + + foreach ($this->storage->disk()->files('packs/' . $pack->uuid) as $file) { + $this->archive->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); + } + + $this->archive->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); + $this->archive->close(); + } else { + $fp = fopen($filename, 'a+'); + fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); + fclose($fp); + } + + return $filename; + } +} diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php new file mode 100644 index 000000000..80b452d88 --- /dev/null +++ b/app/Services/Packs/PackCreationService.php @@ -0,0 +1,104 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Packs; + +use Ramsey\Uuid\Uuid; +use Illuminate\Http\UploadedFile; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; + +class PackCreationService +{ + const VALID_UPLOAD_TYPES = [ + 'application/gzip', + 'application/x-gzip', + ]; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * PackCreationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + FilesystemFactory $storage, + PackRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->storage = $storage; + } + + /** + * Add a new service pack to the system. + * + * @param array $data + * @param \Illuminate\Http\UploadedFile|null $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function handle(array $data, UploadedFile $file = null) + { + if (! is_null($file)) { + if (! $file->isValid()) { + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); + } + + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); + } + } + + // Transform values to boolean + $data['selectable'] = isset($data['selectable']); + $data['visible'] = isset($data['visible']); + $data['locked'] = isset($data['locked']); + + $this->connection->beginTransaction(); + $pack = $this->repository->create(array_merge( + ['uuid' => Uuid::uuid4()], + $data + )); + + $this->storage->disk()->makeDirectory('packs/' . $pack->uuid); + if (! is_null($file)) { + $file->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); + } + + $this->connection->commit(); + + return $pack; + } +} diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php new file mode 100644 index 000000000..9d4743310 --- /dev/null +++ b/app/Services/Packs/PackDeletionService.php @@ -0,0 +1,85 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Packs; + +use Pterodactyl\Models\Pack; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; + +class PackDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * PackDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + ConnectionInterface $connection, + FilesystemFactory $storage, + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->storage = $storage; + } + + /** + * Delete a pack from the database as well as the archive stored on the server. + * + * @param int|\Pterodactyl\Models\Pack$pack + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->setColumns(['id', 'uuid'])->find($pack); + } + + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); + if ($count !== 0) { + throw new HasActiveServersException(trans('exceptions.packs.delete_has_servers')); + } + + $this->connection->beginTransaction(); + $this->repository->delete($pack->id); + $this->storage->disk()->deleteDirectory('packs/' . $pack->uuid); + $this->connection->commit(); + } +} diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php new file mode 100644 index 000000000..a22a55b9f --- /dev/null +++ b/app/Services/Packs/PackUpdateService.php @@ -0,0 +1,75 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Packs; + +use Pterodactyl\Models\Pack; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class PackUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * PackUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Update a pack. + * + * @param int|\Pterodactyl\Models\Pack $pack + * @param array $data + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack, array $data) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->setColumns(['id', 'egg_id'])->find($pack); + } + + if ((int) array_get($data, 'egg_id', $pack->egg_id) !== $pack->egg_id) { + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); + + if ($count !== 0) { + throw new HasActiveServersException(trans('exceptions.packs.update_has_servers')); + } + } + + // Transform values to boolean + $data['selectable'] = isset($data['selectable']); + $data['visible'] = isset($data['visible']); + $data['locked'] = isset($data['locked']); + + return $this->repository->withoutFreshModel()->update($pack->id, $data); + } +} diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php new file mode 100644 index 000000000..6495edd68 --- /dev/null +++ b/app/Services/Packs/TemplateUploadService.php @@ -0,0 +1,125 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Packs; + +use ZipArchive; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; +use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; +use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; + +class TemplateUploadService +{ + const VALID_UPLOAD_TYPES = [ + 'application/zip', + 'text/plain', + 'application/json', + ]; + + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * TemplateUploadService constructor. + * + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \ZipArchive $archive + */ + public function __construct( + PackCreationService $creationService, + ZipArchive $archive + ) { + $this->archive = $archive; + $this->creationService = $creationService; + } + + /** + * Process an uploaded file to create a new pack from a JSON or ZIP format. + * + * @param int $egg + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + public function handle($egg, UploadedFile $file) + { + if (! $file->isValid()) { + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); + } + + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); + } + + if ($file->getMimeType() === 'application/zip') { + return $this->handleArchive($egg, $file); + } else { + $json = json_decode($file->openFile()->fread($file->getSize()), true); + $json['egg_id'] = $egg; + + return $this->creationService->handle($json); + } + } + + /** + * Process a ZIP file to create a pack and stored archive. + * + * @param int $egg + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + protected function handleArchive($egg, $file) + { + if (! $this->archive->open($file->getRealPath())) { + throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable')); + } + + if (! $this->archive->locateName('import.json') || ! $this->archive->locateName('archive.tar.gz')) { + throw new InvalidPackArchiveFormatException(trans('exceptions.packs.invalid_archive_exception')); + } + + $json = json_decode($this->archive->getFromName('import.json'), true); + $json['egg_id'] = $egg; + + $pack = $this->creationService->handle($json); + if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { + // @todo delete the pack that was created. + throw new ZipExtractionException(trans('exceptions.packs.zip_extraction')); + } + + $this->archive->close(); + + return $pack; + } +} diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php new file mode 100644 index 000000000..ec6ea5f12 --- /dev/null +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -0,0 +1,96 @@ +repository = $repository; + $this->runnerService = $runnerService; + } + + /** + * Set the time that this schedule should be run at. This will override the time + * defined on the schedule itself. Useful for triggering one-off task runs. + * + * @param \Carbon\Carbon $time + * @return $this + */ + public function setRunTimeOverride(Carbon $time) + { + $this->runTimeOverride = $time; + + return $this; + } + + /** + * Process a schedule and push the first task onto the queue worker. + * + * @param \Pterodactyl\Models\Schedule $schedule + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Schedule $schedule) + { + $this->repository->loadTasks($schedule); + + $formattedCron = sprintf('%s %s %s * %s *', + $schedule->cron_minute, + $schedule->cron_hour, + $schedule->cron_day_of_month, + $schedule->cron_day_of_week + ); + + $this->repository->update($schedule->id, [ + 'is_processing' => true, + 'next_run_at' => $this->getRunAtTime($formattedCron), + ]); + + $task = $schedule->getRelation('tasks')->where('sequence_id', 1)->first(); + $this->runnerService->handle($task); + } + + /** + * Get the timestamp to store in the database as the next_run time for a schedule. + * + * @param string $formatted + * @return \DateTime|string + */ + private function getRunAtTime(string $formatted) + { + if ($this->runTimeOverride instanceof Carbon) { + return $this->runTimeOverride->toDateTimeString(); + } + + return CronExpression::factory($formatted)->getNextRunDate(); + } +} diff --git a/app/Services/Schedules/ScheduleCreationService.php b/app/Services/Schedules/ScheduleCreationService.php new file mode 100644 index 000000000..9f676e246 --- /dev/null +++ b/app/Services/Schedules/ScheduleCreationService.php @@ -0,0 +1,98 @@ +connection = $connection; + $this->repository = $repository; + $this->taskCreationService = $taskCreationService; + } + + /** + * Create a new schedule for a specific server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @param array $tasks + * @return \Pterodactyl\Models\Schedule + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException + */ + public function handle(Server $server, array $data, array $tasks = []) + { + $data = array_merge($data, [ + 'server_id' => $server->id, + 'next_run_at' => $this->getCronTimestamp($data), + ]); + + $this->connection->beginTransaction(); + $schedule = $this->repository->create($data); + + foreach ($tasks as $index => $task) { + $this->taskCreationService->handle($schedule, [ + 'time_interval' => array_get($task, 'time_interval'), + 'time_value' => array_get($task, 'time_value'), + 'sequence_id' => $index + 1, + 'action' => array_get($task, 'action'), + 'payload' => array_get($task, 'payload'), + ], false); + } + + $this->connection->commit(); + + return $schedule; + } + + /** + * Return a DateTime object after parsing the cron data provided. + * + * @param array $data + * @return \DateTime + */ + private function getCronTimestamp(array $data) + { + $formattedCron = sprintf('%s %s %s * %s *', + array_get($data, 'cron_minute', '*'), + array_get($data, 'cron_hour', '*'), + array_get($data, 'cron_day_of_month', '*'), + array_get($data, 'cron_day_of_week', '*') + ); + + return CronExpression::factory($formattedCron)->getNextRunDate(); + } +} diff --git a/app/Services/Schedules/ScheduleUpdateService.php b/app/Services/Schedules/ScheduleUpdateService.php new file mode 100644 index 000000000..96da1b106 --- /dev/null +++ b/app/Services/Schedules/ScheduleUpdateService.php @@ -0,0 +1,110 @@ +connection = $connection; + $this->repository = $repository; + $this->taskCreationService = $taskCreationService; + $this->taskRepository = $taskRepository; + } + + /** + * Update an existing schedule by deleting all current tasks and re-inserting the + * new values. + * + * @param \Pterodactyl\Models\Schedule $schedule + * @param array $data + * @param array $tasks + * @return \Pterodactyl\Models\Schedule + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException + */ + public function handle(Schedule $schedule, array $data, array $tasks): Schedule + { + $data = array_merge($data, [ + 'next_run_at' => $this->getCronTimestamp($data), + ]); + + $this->connection->beginTransaction(); + + $schedule = $this->repository->update($schedule->id, $data); + $this->taskRepository->deleteWhere([['schedule_id', '=', $schedule->id]]); + + foreach ($tasks as $index => $task) { + $this->taskCreationService->handle($schedule, [ + 'time_interval' => array_get($task, 'time_interval'), + 'time_value' => array_get($task, 'time_value'), + 'sequence_id' => $index + 1, + 'action' => array_get($task, 'action'), + 'payload' => array_get($task, 'payload'), + ], false); + } + + $this->connection->commit(); + + return $schedule; + } + + /** + * Return a DateTime object after parsing the cron data provided. + * + * @param array $data + * @return \DateTime + */ + private function getCronTimestamp(array $data) + { + $formattedCron = sprintf('%s %s %s * %s *', + array_get($data, 'cron_minute', '*'), + array_get($data, 'cron_hour', '*'), + array_get($data, 'cron_day_of_month', '*'), + array_get($data, 'cron_day_of_week', '*') + ); + + return CronExpression::factory($formattedCron)->getNextRunDate(); + } +} diff --git a/app/Services/Schedules/Tasks/RunTaskService.php b/app/Services/Schedules/Tasks/RunTaskService.php new file mode 100644 index 000000000..5d83db077 --- /dev/null +++ b/app/Services/Schedules/Tasks/RunTaskService.php @@ -0,0 +1,53 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Schedules\Tasks; + +use Pterodactyl\Models\Task; +use Pterodactyl\Jobs\Schedule\RunTaskJob; +use Illuminate\Foundation\Bus\DispatchesJobs; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; + +class RunTaskService +{ + use DispatchesJobs; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface + */ + protected $repository; + + /** + * RunTaskService constructor. + * + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository + */ + public function __construct(TaskRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Push a single task onto the queue. + * + * @param int|\Pterodactyl\Models\Task $task + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($task) + { + if (! $task instanceof Task) { + $task = $this->repository->find($task); + } + + $this->repository->update($task->id, ['is_queued' => true]); + $this->dispatch((new RunTaskJob($task->id, $task->schedule_id))->delay($task->time_offset)); + } +} diff --git a/app/Services/Schedules/Tasks/TaskCreationService.php b/app/Services/Schedules/Tasks/TaskCreationService.php new file mode 100644 index 000000000..6cf4fc384 --- /dev/null +++ b/app/Services/Schedules/Tasks/TaskCreationService.php @@ -0,0 +1,70 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Schedules\Tasks; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Schedule; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException; + +class TaskCreationService +{ + const MAX_INTERVAL_TIME_SECONDS = 900; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface + */ + protected $repository; + + /** + * TaskCreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository + */ + public function __construct(TaskRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new task that is assigned to a schedule. + * + * @param int|\Pterodactyl\Models\Schedule $schedule + * @param array $data + * @param bool $returnModel + * @return bool|\Pterodactyl\Models\Task + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException + */ + public function handle($schedule, array $data, $returnModel = true) + { + Assert::true(($schedule instanceof Schedule || is_digit($schedule)), + 'First argument passed to handle must be numeric or instance of \Pterodactyl\Models\Schedule, received %s.' + ); + + $schedule = ($schedule instanceof Schedule) ? $schedule->id : $schedule; + $delay = $data['time_interval'] === 'm' ? $data['time_value'] * 60 : $data['time_value']; + if ($delay > self::MAX_INTERVAL_TIME_SECONDS) { + throw new TaskIntervalTooLongException(trans('exceptions.tasks.chain_interval_too_long')); + } + + $repository = ($returnModel) ? $this->repository : $this->repository->withoutFreshModel(); + $task = $repository->create([ + 'schedule_id' => $schedule, + 'sequence_id' => $data['sequence_id'], + 'action' => $data['action'], + 'payload' => $data['payload'], + 'time_offset' => $delay, + ], false); + + return $task; + } +} diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php new file mode 100644 index 000000000..8924b2a04 --- /dev/null +++ b/app/Services/Servers/BuildModificationService.php @@ -0,0 +1,179 @@ +allocationRepository = $allocationRepository; + $this->daemonServerRepository = $daemonServerRepository; + $this->connection = $connection; + $this->repository = $repository; + } + + /** + * Change the build details for a specified server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server, array $data) + { + $build = []; + $this->connection->beginTransaction(); + + $this->processAllocations($server, $data); + if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { + try { + $allocation = $this->allocationRepository->findFirstWhere([ + ['id', '=', $data['allocation_id']], + ['server_id', '=', $server->id], + ]); + } catch (RecordNotFoundException $ex) { + throw new DisplayException(trans('admin/server.exceptions.default_allocation_not_found')); + } + + $build['default'] = ['ip' => $allocation->ip, 'port' => $allocation->port]; + } + + $server = $this->repository->withFreshModel()->update($server->id, [ + 'memory' => array_get($data, 'memory'), + 'swap' => array_get($data, 'swap'), + 'io' => array_get($data, 'io'), + 'cpu' => array_get($data, 'cpu'), + 'disk' => array_get($data, 'disk'), + 'allocation_id' => array_get($data, 'allocation_id'), + ]); + + $allocations = $this->allocationRepository->findWhere([['server_id', '=', $server->id]]); + + $build['memory'] = (int) $server->memory; + $build['swap'] = (int) $server->swap; + $build['io'] = (int) $server->io; + $build['cpu'] = (int) $server->cpu; + $build['disk'] = (int) $server->disk; + $build['ports|overwrite'] = $allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(); + + try { + $this->daemonServerRepository->setServer($server)->update(['build' => $build]); + $this->connection->commit(); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + + return $server; + } + + /** + * Process the allocations being assigned in the data and ensure they + * are available for a server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + private function processAllocations(Server $server, array &$data) + { + $firstAllocationId = null; + + if (! array_key_exists('add_allocations', $data) && ! array_key_exists('remove_allocations', $data)) { + return; + } + + // Handle the addition of allocations to this server. + if (array_key_exists('add_allocations', $data) && ! empty($data['add_allocations'])) { + $unassigned = $this->allocationRepository->getUnassignedAllocationIds($server->node_id); + + $updateIds = []; + foreach ($data['add_allocations'] as $allocation) { + if (! in_array($allocation, $unassigned)) { + continue; + } + + $firstAllocationId = $firstAllocationId ?? $allocation; + $updateIds[] = $allocation; + } + + if (! empty($updateIds)) { + $this->allocationRepository->updateWhereIn('id', $updateIds, ['server_id' => $server->id]); + } + } + + // Handle removal of allocations from this server. + if (array_key_exists('remove_allocations', $data) && ! empty($data['remove_allocations'])) { + $assigned = $this->allocationRepository->getAssignedAllocationIds($server->id); + + $updateIds = []; + foreach ($data['remove_allocations'] as $allocation) { + if (! in_array($allocation, $assigned)) { + continue; + } + + if ($allocation == $data['allocation_id']) { + if (is_null($firstAllocationId)) { + throw new DisplayException(trans('admin/server.exceptions.no_new_default_allocation')); + } + + $data['allocation_id'] = $firstAllocationId; + } + + $updateIds[] = $allocation; + } + + if (! empty($updateIds)) { + $this->allocationRepository->updateWhereIn('id', $updateIds, ['server_id' => null]); + } + } + } +} diff --git a/app/Services/Servers/ContainerRebuildService.php b/app/Services/Servers/ContainerRebuildService.php new file mode 100644 index 000000000..0dc25b0e4 --- /dev/null +++ b/app/Services/Servers/ContainerRebuildService.php @@ -0,0 +1,42 @@ +repository = $repository; + } + + /** + * Mark a server for rebuild on next boot cycle. + * + * @param \Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function handle(Server $server) + { + try { + $this->repository->setServer($server)->rebuild(); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php new file mode 100644 index 000000000..78d8eb31e --- /dev/null +++ b/app/Services/Servers/DetailsModificationService.php @@ -0,0 +1,86 @@ +connection = $connection; + $this->keyCreationService = $keyCreationService; + $this->keyDeletionService = $keyDeletionService; + $this->repository = $repository; + } + + /** + * Update the details for a single server instance. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @return bool|\Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server, array $data) + { + $this->connection->beginTransaction(); + + $response = $this->repository->setFreshModel($this->getUpdatedModel())->update($server->id, [ + 'owner_id' => array_get($data, 'owner_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description') ?? '', + ], true, true); + + if ((int) array_get($data, 'owner_id', 0) !== (int) $server->owner_id) { + $this->keyDeletionService->handle($server, $server->owner_id); + $this->keyCreationService->handle($server->id, array_get($data, 'owner_id')); + } + + $this->connection->commit(); + + return $response; + } +} diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php new file mode 100644 index 000000000..85eb69bb8 --- /dev/null +++ b/app/Services/Servers/EnvironmentService.php @@ -0,0 +1,110 @@ +config = $config; + $this->repository = $repository; + } + + /** + * Dynamically configure additional environment variables to be assigned + * with a specific server. + * + * @param string $key + * @param callable $closure + */ + public function setEnvironmentKey(string $key, callable $closure) + { + $this->additional[$key] = $closure; + } + + /** + * Return the dynamically added additional keys. + * + * @return array + */ + public function getEnvironmentKeys(): array + { + return $this->additional; + } + + /** + * Take all of the environment variables configured for this server and return + * them in an easy to process format. + * + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server): array + { + $variables = $this->repository->getVariablesWithValues($server->id); + + // Process environment variables defined in this file. This is done first + // in order to allow run-time and config defined variables to take + // priority over built-in values. + foreach ($this->getEnvironmentMappings() as $key => $object) { + $variables[$key] = object_get($server, $object); + } + + // Process variables set in the configuration file. + foreach ($this->config->get('pterodactyl.environment_mappings', []) as $key => $object) { + if (is_callable($object)) { + $variables[$key] = call_user_func($object, $server); + } else { + $variables[$key] = object_get($server, $object); + } + } + + // Process dynamically included environment variables. + foreach ($this->additional as $key => $closure) { + $variables[$key] = call_user_func($closure, $server); + } + + return $variables; + } + + /** + * Return a mapping of Panel default environment variables. + * + * @return array + */ + private function getEnvironmentMappings(): array + { + return [ + 'STARTUP' => 'startup', + 'P_SERVER_LOCATION' => 'location.short', + 'P_SERVER_UUID' => 'uuid', + ]; + } +} diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php new file mode 100644 index 000000000..682813e36 --- /dev/null +++ b/app/Services/Servers/ReinstallServerService.php @@ -0,0 +1,78 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ReinstallServerService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * ReinstallService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->repository = $repository; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function reinstall($server) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + $this->database->beginTransaction(); + $this->repository->withoutFreshModel()->update($server->id, [ + 'installed' => 0, + ]); + + try { + $this->daemonServerRepository->setServer($server)->reinstall(); + $this->database->commit(); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php new file mode 100644 index 000000000..21b52ad2a --- /dev/null +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -0,0 +1,89 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\Server; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class ServerConfigurationStructureService +{ + const REQUIRED_RELATIONS = ['allocation', 'allocations', 'pack', 'option']; + + /** + * @var \Pterodactyl\Services\Servers\EnvironmentService + */ + private $environment; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + private $repository; + + /** + * ServerConfigurationStructureService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Services\Servers\EnvironmentService $environment + */ + public function __construct( + ServerRepositoryInterface $repository, + EnvironmentService $environment + ) { + $this->repository = $repository; + $this->environment = $environment; + } + + /** + * Return a configuration array for a specific server when passed a server model. + * + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server): array + { + if (array_diff(self::REQUIRED_RELATIONS, $server->getRelations())) { + $server = $this->repository->getDataForCreation($server); + } + + $pack = $server->getRelation('pack'); + if (! is_null($pack)) { + $pack = $server->getRelation('pack')->uuid; + } + + return [ + 'uuid' => $server->uuid, + 'build' => [ + 'default' => [ + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, + ], + 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + 'env' => $this->environment->handle($server), + 'memory' => (int) $server->memory, + 'swap' => (int) $server->swap, + 'io' => (int) $server->io, + 'cpu' => (int) $server->cpu, + 'disk' => (int) $server->disk, + 'image' => $server->image, + ], + 'service' => [ + 'egg' => $server->egg->uuid, + 'pack' => $pack, + 'skip_scripts' => $server->skip_scripts, + ], + 'rebuild' => false, + 'suspended' => (int) $server->suspended, + ]; + } +} diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php new file mode 100644 index 000000000..a57e7712b --- /dev/null +++ b/app/Services/Servers/ServerCreationService.php @@ -0,0 +1,289 @@ +allocationSelectionService = $allocationSelectionService; + $this->allocationRepository = $allocationRepository; + $this->configurationStructureService = $configurationStructureService; + $this->connection = $connection; + $this->daemonServerRepository = $daemonServerRepository; + $this->eggRepository = $eggRepository; + $this->findViableNodesService = $findViableNodesService; + $this->repository = $repository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validatorService = $validatorService; + } + + /** + * Create a server on the Panel and trigger a request to the Daemon to begin the server + * creation process. This function will attempt to set as many additional values + * as possible given the input data. For example, if an allocation_id is passed with + * no node_id the node_is will be picked from the allocation. + * + * @param array $data + * @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + */ + public function handle(array $data, DeploymentObject $deployment = null): Server + { + $this->connection->beginTransaction(); + + // If a deployment object has been passed we need to get the allocation + // that the server should use, and assign the node from that allocation. + if ($deployment instanceof DeploymentObject) { + $allocation = $this->configureDeployment($data, $deployment); + $data['allocation_id'] = $allocation->id; + $data['node_id'] = $allocation->node_id; + } + + // Auto-configure the node based on the selected allocation + // if no node was defined. + if (is_null(array_get($data, 'node_id'))) { + $data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']); + } + + if (is_null(array_get($data, 'nest_id'))) { + $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id')); + $data['nest_id'] = $egg->nest_id; + } + + $eggVariableData = $this->validatorService + ->setUserLevel(User::USER_LEVEL_ADMIN) + ->handle(array_get($data, 'egg_id'), array_get($data, 'environment', [])); + + // Create the server and assign any additional allocations to it. + $server = $this->createModel($data); + $this->storeAssignedAllocations($server, $data); + $this->storeEggVariables($server, $eggVariableData); + + $structure = $this->configurationStructureService->handle($server); + + try { + $this->daemonServerRepository->setServer($server)->create($structure, [ + 'start_on_completion' => (bool) array_get($data, 'start_on_completion', false), + ]); + + $this->connection->commit(); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + return $server; + } + + /** + * Gets an allocation to use for automatic deployment. + * + * @param array $data + * @param \Pterodactyl\Models\Objects\DeploymentObject $deployment + * + * @return \Pterodactyl\Models\Allocation + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + */ + private function configureDeployment(array $data, DeploymentObject $deployment): Allocation + { + $nodes = $this->findViableNodesService->setLocations($deployment->getLocations()) + ->setDisk(array_get($data, 'disk')) + ->setMemory(array_get($data, 'memory')) + ->handle(); + + return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) + ->setNodes($nodes) + ->setPorts($deployment->getPorts()) + ->handle(); + } + + /** + * Store the server in the database and return the model. + * + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + private function createModel(array $data): Server + { + return $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'uuidShort' => str_random(8), + 'node_id' => array_get($data, 'node_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description') ?? '', + 'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']), + 'suspended' => false, + 'owner_id' => array_get($data, 'owner_id'), + 'memory' => array_get($data, 'memory'), + 'swap' => array_get($data, 'swap'), + 'disk' => array_get($data, 'disk'), + 'io' => array_get($data, 'io'), + 'cpu' => array_get($data, 'cpu'), + 'oom_disabled' => false, + 'allocation_id' => array_get($data, 'allocation_id'), + 'nest_id' => array_get($data, 'nest_id'), + 'egg_id' => array_get($data, 'egg_id'), + 'pack_id' => (! isset($data['pack_id']) || $data['pack_id'] == 0) ? null : $data['pack_id'], + 'startup' => array_get($data, 'startup'), + 'daemonSecret' => str_random(Node::DAEMON_SECRET_LENGTH), + 'image' => array_get($data, 'image'), + ]); + } + + /** + * Configure the allocations assigned to this server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + */ + private function storeAssignedAllocations(Server $server, array $data) + { + $records = [$data['allocation_id']]; + if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { + $records = array_merge($records, $data['allocation_additional']); + } + + $this->allocationRepository->assignAllocationsToServer($server->id, $records); + } + + /** + * Process environment variables passed for this server and store them in the database. + * + * @param \Pterodactyl\Models\Server $server + * @param \Illuminate\Support\Collection $variables + */ + private function storeEggVariables(Server $server, Collection $variables) + { + $records = $variables->map(function ($result) use ($server) { + return [ + 'server_id' => $server->id, + 'variable_id' => $result->id, + 'variable_value' => $result->value, + ]; + })->toArray(); + + if (! empty($records)) { + $this->serverVariableRepository->insert($records); + } + } + + /** + * Get the node that an allocation belongs to. + * + * @param int $allocation + * @return int + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function getNodeFromAllocation(int $allocation): int + { + $allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($allocation); + + return $allocation->node_id; + } +} diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php new file mode 100644 index 000000000..37481c27e --- /dev/null +++ b/app/Services/Servers/ServerDeletionService.php @@ -0,0 +1,131 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Databases\DatabaseManagementService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ServerDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Pterodactyl\Services\Databases\DatabaseManagementService + */ + protected $databaseManagementService; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + + /** + * @var bool + */ + protected $force = false; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonServerRepositoryInterface $daemonServerRepository, + DatabaseRepositoryInterface $databaseRepository, + DatabaseManagementService $databaseManagementService, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->connection = $connection; + $this->databaseManagementService = $databaseManagementService; + $this->databaseRepository = $databaseRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * 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) + { + $this->force = $bool; + + return $this; + } + + /** + * Delete a server from the panel and remove any associated databases from hosts. + * + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle($server) + { + try { + $this->daemonServerRepository->setServer($server)->delete(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + + if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) { + // If not forcing the deletion, throw an exception, otherwise just log it and + // continue with server deletion process in the panel. + if (! $this->force) { + throw new DaemonConnectionException($exception); + } else { + $this->writer->warning($exception); + } + } + } + + $this->connection->beginTransaction(); + $this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) { + $this->databaseManagementService->delete($item->id); + }); + + $this->repository->delete($server->id); + $this->connection->commit(); + } +} diff --git a/app/Services/Servers/StartupCommandViewService.php b/app/Services/Servers/StartupCommandViewService.php new file mode 100644 index 000000000..7895ec6dd --- /dev/null +++ b/app/Services/Servers/StartupCommandViewService.php @@ -0,0 +1,56 @@ +repository = $repository; + } + + /** + * Generate a startup command for a server and return all of the user-viewable variables + * as well as thier assigned values. + * + * @param int $server + * @return \Illuminate\Support\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $server): Collection + { + $response = $this->repository->getVariablesWithValues($server, true); + $server = $this->repository->getPrimaryAllocation($response->server); + + $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; + $replace = [$server->memory, $server->getRelation('allocation')->ip, $server->getRelation('allocation')->port]; + + $variables = $server->getRelation('egg')->getRelation('variables') + ->each(function ($variable) use (&$find, &$replace, $response) { + $find[] = '{{' . $variable->env_variable . '}}'; + $replace[] = $variable->user_viewable ? $response->data[$variable->env_variable] : '[hidden]'; + })->filter(function ($variable) { + return $variable->user_viewable === 1; + }); + + return collect([ + 'startup' => str_replace($find, $replace, $server->startup), + 'variables' => $variables, + 'server_values' => $response->data, + ]); + } +} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php new file mode 100644 index 000000000..4e954ae1f --- /dev/null +++ b/app/Services/Servers/StartupModificationService.php @@ -0,0 +1,175 @@ +daemonServerRepository = $daemonServerRepository; + $this->connection = $connection; + $this->eggRepository = $eggRepository; + $this->environmentService = $environmentService; + $this->repository = $repository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validatorService = $validatorService; + } + + /** + * Process startup modification for a server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @return \Pterodactyl\Models\Server + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Server $server, array $data): Server + { + $this->connection->beginTransaction(); + if (! is_null(array_get($data, 'environment'))) { + $this->validatorService->setUserLevel($this->getUserLevel()); + $results = $this->validatorService->handle(array_get($data, 'egg_id', $server->egg_id), array_get($data, 'environment', [])); + + $results->each(function ($result) use ($server) { + $this->serverVariableRepository->withoutFreshModel()->updateOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $result->id, + ], [ + 'variable_value' => $result->value, + ]); + }); + } + + $daemonData = []; + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + $this->updateAdministrativeSettings($data, $server, $daemonData); + } + + $daemonData = array_merge_recursive($daemonData, [ + 'build' => [ + 'env|overwrite' => $this->environmentService->handle($server), + ], + ]); + + try { + $this->daemonServerRepository->setServer($server)->update($daemonData); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + $this->connection->commit(); + + return $server; + } + + /** + * Update certain administrative settings for a server in the DB. + * + * @param array $data + * @param \Pterodactyl\Models\Server $server + * @param array $daemonData + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function updateAdministrativeSettings(array $data, Server &$server, array &$daemonData) + { + if ( + is_digit(array_get($data, 'egg_id')) + && $data['egg_id'] != $server->egg_id + && is_null(array_get($data, 'nest_id')) + ) { + $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find($data['egg_id']); + $data['nest_id'] = $egg->nest_id; + } + + $server = $this->repository->update($server->id, [ + 'installed' => 0, + 'startup' => array_get($data, 'startup', $server->startup), + 'nest_id' => array_get($data, 'nest_id', $server->nest_id), + 'egg_id' => array_get($data, 'egg_id', $server->egg_id), + 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, + 'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']), + 'image' => array_get($data, 'docker_image', $server->image), + ]); + + $daemonData = array_merge($daemonData, [ + 'build' => ['image' => $server->image], + 'service' => array_merge( + $this->repository->getDaemonServiceData($server, true), + ['skip_scripts' => $server->skip_scripts] + ), + ]); + } +} diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php new file mode 100644 index 000000000..896cfeb17 --- /dev/null +++ b/app/Services/Servers/SuspensionService.php @@ -0,0 +1,115 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SuspensionService +{ + const ACTION_SUSPEND = 'suspend'; + const ACTION_UNSUSPEND = 'unsuspend'; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * SuspensionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Suspends a server on the system. + * + * @param int|\Pterodactyl\Models\Server $server + * @param string $action + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function toggle($server, $action = self::ACTION_SUSPEND) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + if (! in_array($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND])) { + throw new \InvalidArgumentException(sprintf( + 'Action must be either ' . self::ACTION_SUSPEND . ' or ' . self::ACTION_UNSUSPEND . ', %s passed.', + $action + )); + } + + if ( + $action === self::ACTION_SUSPEND && $server->suspended || + $action === self::ACTION_UNSUSPEND && ! $server->suspended + ) { + return true; + } + + $this->database->beginTransaction(); + $this->repository->withoutFreshModel()->update($server->id, [ + 'suspended' => $action === self::ACTION_SUSPEND, + ]); + + try { + $this->daemonServerRepository->setServer($server)->$action(); + $this->database->commit(); + + return true; + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php new file mode 100644 index 000000000..43685cafb --- /dev/null +++ b/app/Services/Servers/VariableValidatorService.php @@ -0,0 +1,114 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\User; +use Illuminate\Support\Collection; +use Illuminate\Validation\ValidationException; +use Pterodactyl\Traits\Services\HasUserLevels; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; + +class VariableValidatorService +{ + use HasUserLevels; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface + */ + private $optionVariableRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + private $serverRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + */ + private $serverVariableRepository; + + /** + * @var \Illuminate\Contracts\Validation\Factory + */ + private $validator; + + /** + * VariableValidatorService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $optionVariableRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository + * @param \Illuminate\Contracts\Validation\Factory $validator + */ + public function __construct( + EggVariableRepositoryInterface $optionVariableRepository, + ServerRepositoryInterface $serverRepository, + ServerVariableRepositoryInterface $serverVariableRepository, + ValidationFactory $validator + ) { + $this->optionVariableRepository = $optionVariableRepository; + $this->serverRepository = $serverRepository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validator = $validator; + } + + /** + * Validate all of the passed data aganist the given service option variables. + * + * @param int $egg + * @param array $fields + * @return \Illuminate\Support\Collection + * @throws \Illuminate\Validation\ValidationException + */ + public function handle(int $egg, array $fields = []): Collection + { + $variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $egg]]); + + $data = $rules = $customAttributes = []; + foreach ($variables as $variable) { + // Don't attempt to validate variables if they aren't user editable + // and we're not running this at an admin level. + if (! $variable->user_editable && ! $this->isUserLevel(User::USER_LEVEL_ADMIN)) { + continue; + } + + $data['environment'][$variable->env_variable] = array_get($fields, $variable->env_variable); + $rules['environment.' . $variable->env_variable] = $variable->rules; + $customAttributes['environment.' . $variable->env_variable] = trans('validation.internal.variable_value', ['env' => $variable->name]); + } + + $validator = $this->validator->make($data, $rules, [], $customAttributes); + if ($validator->fails()) { + throw new ValidationException($validator); + } + + $response = $variables->filter(function ($item) { + // Skip doing anything if user is not an admin and variable is not user viewable or editable. + if (! $this->isUserLevel(User::USER_LEVEL_ADMIN) && (! $item->user_editable || ! $item->user_viewable)) { + return false; + } + + return true; + })->map(function ($item) use ($fields) { + return (object) [ + 'id' => $item->id, + 'key' => $item->env_variable, + 'value' => array_get($fields, $item->env_variable), + ]; + })->filter(function ($item) { + return is_object($item); + }); + + return $response; + } +} diff --git a/app/Services/Sftp/AuthenticateUsingPasswordService.php b/app/Services/Sftp/AuthenticateUsingPasswordService.php new file mode 100644 index 000000000..f89a32c5d --- /dev/null +++ b/app/Services/Sftp/AuthenticateUsingPasswordService.php @@ -0,0 +1,107 @@ +keyProviderService = $keyProviderService; + $this->repository = $repository; + $this->subuserRepository = $subuserRepository; + $this->userRepository = $userRepository; + } + + /** + * Attempt to authenticate a provded username and password and determine if they + * have permission to access a given server. This function does not account for + * subusers currently. Only administrators and server owners can login to access + * their files at this time. + * + * Server must exist on the node that the API call is being made from in order for a + * valid response to be provided. + * + * @param string $username + * @param string $password + * @param int $node + * @param string|null $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function handle(string $username, string $password, int $node, string $server = null): array + { + if (is_null($server)) { + throw new RecordNotFoundException; + } + + $user = $this->userRepository->setColumns(['id', 'root_admin', 'password'])->findFirstWhere([['username', '=', $username]]); + if (! password_verify($password, $user->password)) { + throw new RecordNotFoundException; + } + + $server = $this->repository->setColumns(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->getByUuid($server); + if ($server->node_id !== $node) { + throw new RecordNotFoundException; + } + + if (! $user->root_admin && $server->owner_id !== $user->id) { + $subuser = $this->subuserRepository->getWithPermissionsUsingUserAndServer($user->id, $server->id); + $permissions = $subuser->getRelation('permissions')->pluck('permission')->toArray(); + + if (! in_array('access-sftp', $permissions)) { + throw new RecordNotFoundException; + } + } + + if ($server->installed !== 1 || $server->suspended) { + throw new BadRequestHttpException; + } + + return [ + 'server' => $server->uuid, + 'token' => $this->keyProviderService->handle($server, $user), + ]; + } +} diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php new file mode 100644 index 000000000..0173e8015 --- /dev/null +++ b/app/Services/Subusers/PermissionCreationService.php @@ -0,0 +1,61 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Subusers; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Permission; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; + +class PermissionCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface + */ + protected $repository; + + /** + * PermissionCreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository + */ + public function __construct(PermissionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Assign permissions to a given subuser. + * + * @param int $subuser + * @param array $permissions + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle($subuser, array $permissions) + { + Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.'); + + $permissionMappings = Permission::getPermissions(true); + $insertPermissions = []; + + foreach ($permissions as $permission) { + if (array_key_exists($permission, $permissionMappings)) { + Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.'); + + array_push($insertPermissions, [ + 'subuser_id' => $subuser, + 'permission' => $permission, + ]); + } + } + + $this->repository->withoutFreshModel()->insert($insertPermissions); + } +} diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php new file mode 100644 index 000000000..430b67617 --- /dev/null +++ b/app/Services/Subusers/SubuserCreationService.php @@ -0,0 +1,138 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Subusers; + +use Pterodactyl\Models\Server; +use Pterodactyl\Rules\Username; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; +use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; + +class SubuserCreationService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + */ + protected $keyCreationService; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService + */ + protected $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $subuserRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $userCreationService; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $userRepository; + + /** + * SubuserCreationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService + * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository + * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository + */ + public function __construct( + ConnectionInterface $connection, + DaemonKeyCreationService $keyCreationService, + PermissionCreationService $permissionService, + ServerRepositoryInterface $serverRepository, + SubuserRepositoryInterface $subuserRepository, + UserCreationService $userCreationService, + UserRepositoryInterface $userRepository + ) { + $this->connection = $connection; + $this->keyCreationService = $keyCreationService; + $this->permissionService = $permissionService; + $this->serverRepository = $serverRepository; + $this->subuserRepository = $subuserRepository; + $this->userRepository = $userRepository; + $this->userCreationService = $userCreationService; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * @param string $email + * @param array $permissions + * @return \Pterodactyl\Models\Subuser + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + */ + public function handle($server, $email, array $permissions) + { + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $this->connection->beginTransaction(); + try { + $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); + + if ($server->owner_id === $user->id) { + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + } + + $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); + if ($subuserCount !== 0) { + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); + } + } catch (RecordNotFoundException $exception) { + $username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')); + $user = $this->userCreationService->handle([ + 'email' => $email, + 'username' => $username . str_random(3), + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ]); + } + + $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); + $this->keyCreationService->handle($server->id, $user->id); + $this->permissionService->handle($subuser->id, $permissions); + $this->connection->commit(); + + return $subuser; + } +} diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php new file mode 100644 index 000000000..6b100cd18 --- /dev/null +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -0,0 +1,66 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Subusers; + +use Pterodactyl\Models\Subuser; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; + +class SubuserDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService + */ + private $keyDeletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + private $repository; + + /** + * SubuserDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + DaemonKeyDeletionService $keyDeletionService, + SubuserRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->keyDeletionService = $keyDeletionService; + $this->repository = $repository; + } + + /** + * Delete a subuser and their associated permissions from the Panel and Daemon. + * + * @param \Pterodactyl\Models\Subuser $subuser + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Subuser $subuser) + { + $this->connection->beginTransaction(); + $this->keyDeletionService->handle($subuser->server_id, $subuser->user_id); + $this->repository->delete($subuser->id); + $this->connection->commit(); + } +} diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php new file mode 100644 index 000000000..f56e47b9d --- /dev/null +++ b/app/Services/Subusers/SubuserUpdateService.php @@ -0,0 +1,107 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Subusers; + +use Pterodactyl\Models\Subuser; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SubuserUpdateService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + private $daemonRepository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + private $keyProviderService; + + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface + */ + private $permissionRepository; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService + */ + private $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + private $repository; + + /** + * SubuserUpdateService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService + * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + DaemonKeyProviderService $keyProviderService, + DaemonServerRepositoryInterface $daemonRepository, + PermissionCreationService $permissionService, + PermissionRepositoryInterface $permissionRepository, + SubuserRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->keyProviderService = $keyProviderService; + $this->permissionRepository = $permissionRepository; + $this->permissionService = $permissionService; + $this->repository = $repository; + } + + /** + * Update permissions for a given subuser. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @param array $permissions + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Subuser $subuser, array $permissions) + { + $subuser = $this->repository->loadServerAndUserRelations($subuser); + + $this->connection->beginTransaction(); + $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); + $this->permissionService->handle($subuser->id, $permissions); + + try { + $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false); + $this->daemonRepository->setServer($subuser->getRelation('server'))->revokeAccessKey($token); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + $this->connection->commit(); + } +} diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php new file mode 100644 index 000000000..6fe8bc9dd --- /dev/null +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -0,0 +1,85 @@ +config = $config; + $this->encrypter = $encrypter; + $this->google2FA = $google2FA; + $this->repository = $repository; + } + + /** + * Toggle 2FA on an account only if the token provided is valid. + * + * @param \Pterodactyl\Models\User $user + * @param string $token + * @param bool|null $toggleState + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + */ + public function handle(User $user, string $token, bool $toggleState = null): bool + { + $window = $this->config->get('pterodactyl.auth.2fa.window'); + $secret = $this->encrypter->decrypt($user->totp_secret); + + $isValidToken = $this->google2FA->verifyKey($secret, $token, $window); + + if (! $isValidToken) { + throw new TwoFactorAuthenticationTokenInvalid; + } + + $this->repository->withoutFreshModel()->update($user->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), + ]); + + return true; + } +} diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php new file mode 100644 index 000000000..4d2ecff8a --- /dev/null +++ b/app/Services/Users/TwoFactorSetupService.php @@ -0,0 +1,81 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Users; + +use Pterodactyl\Models\User; +use PragmaRX\Google2FA\Google2FA; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class TwoFactorSetupService +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + + /** + * @var \PragmaRX\Google2FA\Google2FA + */ + private $google2FA; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + private $repository; + + /** + * TwoFactorSetupService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \PragmaRX\Google2FA\Google2FA $google2FA + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + ConfigRepository $config, + Encrypter $encrypter, + Google2FA $google2FA, + UserRepositoryInterface $repository + ) { + $this->config = $config; + $this->encrypter = $encrypter; + $this->google2FA = $google2FA; + $this->repository = $repository; + } + + /** + * Generate a 2FA token and store it in the database before returning the + * QR code image. + * + * @param \Pterodactyl\Models\User $user + * @return string + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(User $user): string + { + $secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes')); + $image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret); + + $this->repository->withoutFreshModel()->update($user->id, [ + 'totp_secret' => $this->encrypter->encrypt($secret), + ]); + + return $image; + } +} diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php new file mode 100644 index 000000000..f4824e48b --- /dev/null +++ b/app/Services/Users/UserCreationService.php @@ -0,0 +1,85 @@ +connection = $connection; + $this->hasher = $hasher; + $this->passwordService = $passwordService; + $this->repository = $repository; + } + + /** + * Create a new user on the system. + * + * @param array $data + * @return \Pterodactyl\Models\User + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + if (array_key_exists('password', $data) && ! empty($data['password'])) { + $data['password'] = $this->hasher->make($data['password']); + } + + $this->connection->beginTransaction(); + if (! isset($data['password']) || empty($data['password'])) { + $data['password'] = $this->hasher->make(str_random(30)); + $token = $this->passwordService->handle($data['email']); + } + + /** @var \Pterodactyl\Models\User $user */ + $user = $this->repository->create(array_merge($data, [ + 'uuid' => Uuid::uuid4()->toString(), + ]), true, true); + + $this->connection->commit(); + $user->notify(new AccountCreated($user, $token ?? null)); + + return $user; + } +} diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php new file mode 100644 index 000000000..942e76faf --- /dev/null +++ b/app/Services/Users/UserDeletionService.php @@ -0,0 +1,73 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Users; + +use Pterodactyl\Models\User; +use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +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. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + Translator $translator, + UserRepositoryInterface $repository + ) { + $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) + { + if ($user instanceof User) { + $user = $user->id; + } + + $servers = $this->serverRepository->setColumns('id')->findCountWhere([['owner_id', '=', $user]]); + if ($servers > 0) { + throw new DisplayException($this->translator->trans('admin/user.exceptions.user_has_servers')); + } + + return $this->repository->delete($user); + } +} diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php new file mode 100644 index 000000000..440ab57ce --- /dev/null +++ b/app/Services/Users/UserUpdateService.php @@ -0,0 +1,80 @@ +hasher = $hasher; + $this->repository = $repository; + $this->revocationService = $revocationService; + } + + /** + * Update the user model instance. If the user has been removed as an administrator + * revoke all of the authentication tokens that have beenn assigned to their account. + * + * @param \Pterodactyl\Models\User $user + * @param array $data + * @return \Illuminate\Support\Collection + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(User $user, array $data): Collection + { + if (! empty(array_get($data, 'password'))) { + $data['password'] = $this->hasher->make($data['password']); + } else { + unset($data['password']); + } + + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + if (array_get($data, 'root_admin', 0) == 0 && $user->root_admin) { + $this->revocationService->handle($user, array_get($data, 'ignore_connection_error', false)); + } + } else { + unset($data['root_admin']); + } + + return collect([ + 'model' => $this->repository->update($user->id, $data), + 'exceptions' => $this->revocationService->getExceptions(), + ]); + } +} diff --git a/app/Services/UuidService.php b/app/Services/UuidService.php deleted file mode 100644 index 2b043731a..000000000 --- a/app/Services/UuidService.php +++ /dev/null @@ -1,76 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use DB; -use Uuid; - -class UuidService -{ - /** - * Generate a unique UUID validating against specified table and column. - * Defaults to `users.uuid`. - * - * @param string $table - * @param string $field - * @param int $type - * @return string - */ - public function generate($table = 'users', $field = 'uuid', $type = 4) - { - $return = false; - do { - $uuid = Uuid::generate($type); - if (! DB::table($table)->where($field, $uuid)->exists()) { - $return = $uuid; - } - } while (! $return); - - return (string) $return; - } - - /** - * Generates a ShortUUID code which is 8 characters long and is used for identifying servers in the system. - * - * @param string $table - * @param string $field - * @param null|string $attachedUuid - * @return string - */ - public function generateShort($table = 'servers', $field = 'uuidShort', $attachedUuid = null) - { - $return = false; - do { - $short = (is_null($attachedUuid)) ? substr(Uuid::generate(4), 0, 8) : substr($attachedUuid, 0, 8); - $attachedUuid = null; - - if (! DB::table($table)->where($field, $short)->exists()) { - $return = $short; - } - } while (! $return); - - return (string) $return; - } -} diff --git a/app/Services/VersionService.php b/app/Services/VersionService.php deleted file mode 100644 index e3eaf8ade..000000000 --- a/app/Services/VersionService.php +++ /dev/null @@ -1,135 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use Cache; -use GuzzleHttp\Client; - -class VersionService -{ - /** - * The cached CDN response. - * - * @var object - */ - protected static $versions; - - /** - * Version constructor. - * - * @return void - */ - public function __construct() - { - self::$versions = Cache::remember('versions', config('pterodactyl.cdn.cache'), function () { - $client = new Client(); - - try { - $response = $client->request('GET', config('pterodactyl.cdn.url')); - - if ($response->getStatusCode() === 200) { - return json_decode($response->getBody()); - } else { - throw new \Exception('Invalid response code.'); - } - } catch (\Exception $ex) { - // Failed request, just return errored version. - return (object) [ - 'panel' => 'error', - 'daemon' => 'error', - 'discord' => 'https://pterodactyl.io/discord', - ]; - } - }); - } - - /** - * Return current panel version from CDN. - * - * @return string - */ - public static function getPanel() - { - return self::$versions->panel; - } - - /** - * Return current daemon version from CDN. - * - * @return string - */ - public static function getDaemon() - { - return self::$versions->daemon; - } - - /** - * Return Discord link from CDN. - * - * @return string - */ - public static function getDiscord() - { - return self::$versions->discord; - } - - /** - * Return current panel version. - * - * @return null|string - */ - public function getCurrentPanel() - { - return config('app.version'); - } - - /** - * Determine if panel is latest version. - * - * @return bool - */ - public static function isLatestPanel() - { - if (config('app.version') === 'canary') { - return true; - } - - return version_compare(config('app.version'), self::$versions->panel) >= 0; - } - - /** - * Determine if daemon is latest version. - * - * @return bool - */ - public static function isLatestDaemon($daemon) - { - if ($daemon === '0.0.0-canary') { - return true; - } - - return version_compare($daemon, self::$versions->daemon) >= 0; - } -} diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php new file mode 100644 index 000000000..bc4d2486e --- /dev/null +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -0,0 +1,48 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Traits\Commands; + +use Pterodactyl\Exceptions\PterodactylException; + +trait EnvironmentWriterTrait +{ + /** + * Update the .env file for the application using the passed in values. + * + * @param array $values + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function writeToEnvironment(array $values = []) + { + $path = base_path('.env'); + if (! file_exists($path)) { + throw new PterodactylException('Cannot locate .env file, was this software installed correctly?'); + } + + $saveContents = file_get_contents($path); + collect($values)->each(function ($value, $key) use (&$saveContents) { + $key = strtoupper($key); + if (str_contains($value, ' ') && ! preg_match('/\"(.*)\"/', $value)) { + $value = sprintf('"%s"', addslashes($value)); + } + + $saveValue = sprintf('%s=%s', $key, $value); + + if (preg_match_all('/^' . $key . '=(.*)$/m', $saveContents) < 1) { + $saveContents = $saveContents . PHP_EOL . $saveValue; + } else { + $saveContents = preg_replace('/^' . $key . '=(.*)$/m', $saveValue, $saveContents); + } + }); + + file_put_contents($path, $saveContents); + } +} diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php new file mode 100644 index 000000000..bbda917d1 --- /dev/null +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -0,0 +1,64 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Traits\Controllers; + +use Javascript; +use Illuminate\Http\Request; + +trait JavascriptInjection +{ + /** + * @var \Illuminate\Http\Request + */ + private $request; + + /** + * Set the request object to use when injecting JS. + * + * @param \Illuminate\Http\Request $request + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + + /** + * Injects server javascript into the page to be used by other services. + * + * @param array $args + * @param bool $overwrite + * @return array + */ + public function injectJavascript($args = [], $overwrite = false) + { + $request = $this->request ?? app()->make(Request::class); + $server = $request->attributes->get('server'); + $token = $request->attributes->get('server_token'); + + $response = array_merge_recursive([ + 'server' => [ + 'uuid' => $server->uuid, + 'uuidShort' => $server->uuidShort, + 'daemonSecret' => $token, + ], + 'server_token' => $token, + 'node' => [ + 'fqdn' => $server->node->fqdn, + 'scheme' => $server->node->scheme, + 'daemonListen' => $server->node->daemonListen, + ], + ], $args); + + return Javascript::put($overwrite ? $args : $response); + } +} diff --git a/app/Traits/Helpers/AvailableLanguages.php b/app/Traits/Helpers/AvailableLanguages.php new file mode 100644 index 000000000..f44771f6d --- /dev/null +++ b/app/Traits/Helpers/AvailableLanguages.php @@ -0,0 +1,56 @@ +getFilesystemInstance()->directories(resource_path('lang')))->mapWithKeys(function ($path) use ($localize) { + $code = basename($path); + $value = $localize ? $this->getIsoInstance()->nativeByCode1($code) : $this->getIsoInstance()->languageByCode1($code); + + return [$code => title_case($value)]; + })->toArray(); + } + + /** + * Return an instance of the filesystem for getting a folder listing. + * + * @return \Illuminate\Filesystem\Filesystem + */ + private function getFilesystemInstance(): Filesystem + { + return $this->filesystem = $this->filesystem ?: app()->make(Filesystem::class); + } + + /** + * Return an instance of the ISO639 class for generating names. + * + * @return \Matriphe\ISO639\ISO639 + */ + private function getIsoInstance(): ISO639 + { + return $this->iso639 = $this->iso639 ?: app()->make(ISO639::class); + } +} diff --git a/app/Traits/Services/HasUserLevels.php b/app/Traits/Services/HasUserLevels.php new file mode 100644 index 000000000..29e49e8e6 --- /dev/null +++ b/app/Traits/Services/HasUserLevels.php @@ -0,0 +1,47 @@ +userLevel = $level; + + return $this; + } + + /** + * Determine which level this function is running at. + * + * @return int + */ + public function getUserLevel(): int + { + return $this->userLevel; + } + + /** + * Determine if the current user level is set to a specific level. + * + * @param int $level + * @return bool + */ + public function isUserLevel(int $level): bool + { + return $this->getUserLevel() === $level; + } +} diff --git a/app/Traits/Services/ReturnsUpdatedModels.php b/app/Traits/Services/ReturnsUpdatedModels.php new file mode 100644 index 000000000..2d5ee64fd --- /dev/null +++ b/app/Traits/Services/ReturnsUpdatedModels.php @@ -0,0 +1,35 @@ +updatedModel; + } + + /** + * 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. + * + * @param bool $toggle + * + * @return $this + */ + public function returnUpdatedModel(bool $toggle = true) + { + $this->updatedModel = $toggle; + + return $this; + } +} diff --git a/app/Transformers/Admin/AllocationTransformer.php b/app/Transformers/Admin/AllocationTransformer.php deleted file mode 100644 index e7bd15c36..000000000 --- a/app/Transformers/Admin/AllocationTransformer.php +++ /dev/null @@ -1,98 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Allocation; -use League\Fractal\TransformerAbstract; - -class AllocationTransformer extends TransformerAbstract -{ - /** - * The filter to be applied to this transformer. - * - * @var bool|string - */ - protected $filter; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @param bool $filter - * @return void - */ - public function __construct($request = false, $filter = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - $this->filter = $filter; - } - - /** - * Return a generic transformed allocation array. - * - * @return array - */ - public function transform(Allocation $allocation) - { - return $this->transformWithFilter($allocation); - } - - /** - * Determine which transformer filter to apply. - * - * @return array - */ - protected function transformWithFilter(Allocation $allocation) - { - if ($this->filter === 'server') { - return $this->transformForServer($allocation); - } - - return $allocation->toArray(); - } - - /** - * Transform the allocation to only return information not duplicated - * in the server response (discard node_id and server_id). - * - * @return array - */ - protected function transformForServer(Allocation $allocation) - { - return collect($allocation)->only('id', 'ip', 'ip_alias', 'port')->toArray(); - } -} diff --git a/app/Transformers/Admin/LocationTransformer.php b/app/Transformers/Admin/LocationTransformer.php deleted file mode 100644 index f3fd95885..000000000 --- a/app/Transformers/Admin/LocationTransformer.php +++ /dev/null @@ -1,102 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Location; -use League\Fractal\TransformerAbstract; - -class LocationTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'nodes', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed pack array. - * - * @return array - */ - public function transform(Location $location) - { - return $location->toArray(); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(Location $location) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($location->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeNodes(Location $location) - { - if ($this->request && ! $this->request->apiKeyHasPermission('node-list')) { - return; - } - - return $this->collection($location->nodes, new NodeTransformer($this->request), 'node'); - } -} diff --git a/app/Transformers/Admin/NodeTransformer.php b/app/Transformers/Admin/NodeTransformer.php deleted file mode 100644 index d18b64f23..000000000 --- a/app/Transformers/Admin/NodeTransformer.php +++ /dev/null @@ -1,117 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Node; -use League\Fractal\TransformerAbstract; - -class NodeTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'allocations', - 'location', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed pack array. - * - * @return array - */ - public function transform(Node $node) - { - return $node->toArray(); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAllocations(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('node-view')) { - return; - } - - return $this->collection($node->allocations, new AllocationTransformer($this->request), 'allocation'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeLocation(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('location-list')) { - return; - } - - return $this->item($node->location, new LocationTransformer($this->request), 'location'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($node->servers, new ServerTransformer($this->request), 'server'); - } -} diff --git a/app/Transformers/Admin/OptionTransformer.php b/app/Transformers/Admin/OptionTransformer.php deleted file mode 100644 index 5b86b53d6..000000000 --- a/app/Transformers/Admin/OptionTransformer.php +++ /dev/null @@ -1,132 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\ServiceOption; -use League\Fractal\TransformerAbstract; - -class OptionTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'service', - 'packs', - 'servers', - 'variables', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed service option array. - * - * @return array - */ - public function transform(ServiceOption $option) - { - return $option->toArray(); - } - - /** - * Return the parent service for this service option. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeService(ServiceOption $option) - { - if ($this->request && ! $this->request->apiKeyHasPermission('service-view')) { - return; - } - - return $this->item($option->service, new ServiceTransformer($this->request), 'service'); - } - - /** - * Return the packs associated with this service option. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includePacks(ServiceOption $option) - { - if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { - return; - } - - return $this->collection($option->packs, new PackTransformer($this->request), 'pack'); - } - - /** - * Return the servers associated with this service option. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(ServiceOption $option) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($option->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the variables for this service option. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeVariables(ServiceOption $option) - { - if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { - return; - } - - return $this->collection($option->variables, new ServiceVariableTransformer($this->request), 'variable'); - } -} diff --git a/app/Transformers/Admin/ServerTransformer.php b/app/Transformers/Admin/ServerTransformer.php deleted file mode 100644 index 4d94b7e10..000000000 --- a/app/Transformers/Admin/ServerTransformer.php +++ /dev/null @@ -1,207 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use League\Fractal\TransformerAbstract; - -class ServerTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'allocations', - 'user', - 'subusers', - 'pack', - 'service', - 'option', - 'variables', - 'location', - 'node', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed server array. - * - * @return array - */ - public function transform(Server $server) - { - return collect($server->toArray())->only($server->getTableColumns())->toArray(); - } - - /** - * Return a generic array of allocations for this server. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAllocations(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { - return; - } - - return $this->collection($server->allocations, new AllocationTransformer($this->request, 'server'), 'allocation'); - } - - /** - * Return a generic array of data about subusers for this server. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeSubusers(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { - return; - } - - return $this->collection($server->subusers, new SubuserTransformer($this->request), 'subuser'); - } - - /** - * Return a generic array of data about subusers for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeUser(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('user-view')) { - return; - } - - return $this->item($server->user, new UserTransformer($this->request), 'user'); - } - - /** - * Return a generic array with pack information for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includePack(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('pack-view')) { - return; - } - - return $this->item($server->pack, new PackTransformer($this->request), 'pack'); - } - - /** - * Return a generic array with service information for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeService(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('service-view')) { - return; - } - - return $this->item($server->service, new ServiceTransformer($this->request), 'service'); - } - - /** - * Return a generic array with service option information for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeOption(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { - return; - } - - return $this->item($server->option, new OptionTransformer($this->request), 'option'); - } - - /** - * Return a generic array of data about subusers for this server. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeVariables(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { - return; - } - - return $this->collection($server->variables, new ServerVariableTransformer($this->request), 'server_variable'); - } - - /** - * Return a generic array with pack information for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeLocation(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('location-list')) { - return; - } - - return $this->item($server->location, new LocationTransformer($this->request), 'location'); - } - - /** - * Return a generic array with pack information for this server. - * - * @return \Leauge\Fractal\Resource\Item|void - */ - public function includeNode(Server $server) - { - if ($this->request && ! $this->request->apiKeyHasPermission('node-view')) { - return; - } - - return $this->item($server->node, new NodeTransformer($this->request), 'node'); - } -} diff --git a/app/Transformers/Admin/ServerVariableTransformer.php b/app/Transformers/Admin/ServerVariableTransformer.php deleted file mode 100644 index 3211e0295..000000000 --- a/app/Transformers/Admin/ServerVariableTransformer.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\ServerVariable; -use League\Fractal\TransformerAbstract; - -class ServerVariableTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = ['parent']; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed server variable array. - * - * @return array - */ - public function transform(ServerVariable $variable) - { - return $variable->toArray(); - } - - /** - * Return the parent service variable data. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeParent(ServerVariable $variable) - { - if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { - return; - } - - return $this->item($variable->variable, new ServiceVariableTransformer($this->request), 'variable'); - } -} diff --git a/app/Transformers/Admin/ServiceTransformer.php b/app/Transformers/Admin/ServiceTransformer.php deleted file mode 100644 index 2df1fc8cc..000000000 --- a/app/Transformers/Admin/ServiceTransformer.php +++ /dev/null @@ -1,117 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use League\Fractal\TransformerAbstract; - -class ServiceTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'options', - 'servers', - 'packs', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed service array. - * - * @return array - */ - public function transform(Service $service) - { - return $service->toArray(); - } - - /** - * Return the the service options. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeOptions(Service $service) - { - if ($this->request && ! $this->request->apiKeyHasPermission('option-list')) { - return; - } - - return $this->collection($service->options, new OptionTransformer($this->request), 'option'); - } - - /** - * Return the servers associated with this service. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(Service $service) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($service->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the packs associated with this service. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includePacks(Service $service) - { - if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { - return; - } - - return $this->collection($service->packs, new PackTransformer($this->request), 'pack'); - } -} diff --git a/app/Transformers/Admin/ServiceVariableTransformer.php b/app/Transformers/Admin/ServiceVariableTransformer.php deleted file mode 100644 index aa10428d9..000000000 --- a/app/Transformers/Admin/ServiceVariableTransformer.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use League\Fractal\TransformerAbstract; -use Pterodactyl\Models\ServiceVariable; - -class ServiceVariableTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = ['variables']; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed server variable array. - * - * @return array - */ - public function transform(ServiceVariable $variable) - { - return $variable->toArray(); - } - - /** - * Return the server variables associated with this variable. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeVariables(ServiceVariable $variable) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { - return; - } - - return $this->collection($variable->serverVariable, new ServerVariableTransformer($this->request), 'server_variable'); - } -} diff --git a/app/Transformers/Admin/UserTransformer.php b/app/Transformers/Admin/UserTransformer.php deleted file mode 100644 index 0d26961b9..000000000 --- a/app/Transformers/Admin/UserTransformer.php +++ /dev/null @@ -1,102 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use League\Fractal\TransformerAbstract; - -class UserTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'access', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @return void - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed subuser array. - * - * @return array - */ - public function transform(User $user) - { - return $user->toArray(); - } - - /** - * Return the servers associated with this user. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(User $user) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($user->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the servers that this user can access. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAccess(User $user) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($user->access()->get(), new ServerTransformer($this->request), 'server'); - } -} diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php new file mode 100644 index 000000000..cce6acc86 --- /dev/null +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -0,0 +1,81 @@ + $allocation->id, + 'ip' => $allocation->ip, + 'alias' => $allocation->ip_alias, + 'port' => $allocation->port, + 'assigned' => ! is_null($allocation->server_id), + ]; + } + + /** + * Load the node relationship onto a given transformation. + * + * @param \Pterodactyl\Models\Allocation $allocation + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeNode(Allocation $allocation) + { + if (! $this->authorize(AdminAcl::RESOURCE_NODES)) { + return $this->null(); + } + + $allocation->loadMissing('node'); + + return $this->item( + $allocation->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node' + ); + } + + /** + * Load the server relationship onto a given transformation. + * + * @param \Pterodactyl\Models\Allocation $allocation + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeServer(Allocation $allocation) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $allocation->loadMissing('server'); + + return $this->item( + $allocation->getRelation('server'), $this->makeTransformer(ServerTransformer::class), 'server' + ); + } +} diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php new file mode 100644 index 000000000..c2fdf6137 --- /dev/null +++ b/app/Transformers/Api/Application/BaseTransformer.php @@ -0,0 +1,103 @@ +call([$this, 'handle']); + } + } + + /** + * Set the HTTP request class being used for this request. + * + * @param \Pterodactyl\Models\ApiKey $key + * @return $this + */ + public function setKey(ApiKey $key) + { + $this->key = $key; + + return $this; + } + + /** + * Return the request instance being used for this transformer. + * + * @return \Pterodactyl\Models\ApiKey + */ + public function getKey(): ApiKey + { + return $this->key; + } + + /** + * 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. + * + * @param string $resource + * @return bool + */ + protected function authorize(string $resource): bool + { + return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); + } + + /** + * Create a new instance of the transformer and pass along the currently + * set API key. + * + * @param string $abstract + * @param array $parameters + * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer + */ + protected function makeTransformer(string $abstract, array $parameters = []): self + { + /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ + $transformer = Container::getInstance()->makeWith($abstract, $parameters); + $transformer->setKey($this->getKey()); + + return $transformer; + } + + /** + * Return an ISO-8601 formatted timestamp to use in the API response. + * + * @param string $timestamp + * @return string + */ + protected function formatTimestamp(string $timestamp): string + { + return Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $timestamp) + ->setTimezone(self::RESPONSE_TIMEZONE) + ->toIso8601String(); + } +} diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php new file mode 100644 index 000000000..ef7d575bf --- /dev/null +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -0,0 +1,69 @@ + $model->id, + 'name' => $model->name, + 'host' => $model->host, + 'port' => $model->port, + 'username' => $model->username, + 'node' => $model->node_id, + 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->created_at) + ->setTimezone(config('app.timezone')) + ->toIso8601String(), + 'updated_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->updated_at) + ->setTimezone(config('app.timezone')) + ->toIso8601String(), + ]; + } + + /** + * Include the databases associated with this host. + * + * @param \Pterodactyl\Models\DatabaseHost $model + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeDatabases(DatabaseHost $model) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVER_DATABASES)) { + return $this->null(); + } + + $model->loadMissing('databases'); + + return $this->collection($model->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), Database::RESOURCE_NAME); + } +} diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php new file mode 100644 index 000000000..3454547bd --- /dev/null +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -0,0 +1,150 @@ + $model->id, + 'uuid' => $model->uuid, + 'nest' => $model->nest_id, + 'author' => $model->author, + 'description' => $model->description, + 'docker_image' => $model->docker_image, + 'config' => [ + 'files' => json_decode($model->config_files), + 'startup' => json_decode($model->config_startup), + 'stop' => $model->config_stop, + 'logs' => json_decode($model->config_logs), + 'extends' => $model->config_from, + ], + 'startup' => $model->startup, + 'script' => [ + 'privileged' => $model->script_is_privileged, + 'install' => $model->script_install, + 'entry' => $model->script_entry, + 'container' => $model->script_container, + 'extends' => $model->copy_script_from, + ], + $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), + $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), + ]; + } + + /** + * Include the Nest relationship for the given Egg in the transformation. + * + * @param \Pterodactyl\Models\Egg $model + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeNest(Egg $model) + { + 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. + * + * @param \Pterodactyl\Models\Egg $model + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeServers(Egg $model) + { + 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. + * + * @param \Pterodactyl\Models\Egg $model + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeConfig(Egg $model) + { + if (is_null($model->config_from)) { + return $this->null(); + } + + $model->loadMissing('configFrom'); + + return $this->item($model, function (Egg $model) { + return [ + '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), + ]; + }); + } + + /** + * Include more detailed information about the script configuration if the + * Egg is extending another. + * + * @param \Pterodactyl\Models\Egg $model + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeScript(Egg $model) + { + if (is_null($model->copy_script_from)) { + return $this->null(); + } + + $model->loadMissing('scriptFrom'); + + return $this->item($model, function (Egg $model) { + return [ + 'privileged' => $model->script_is_privileged, + 'install' => $model->copy_script_install, + 'entry' => $model->copy_script_entry, + 'container' => $model->copy_script_container, + ]; + }); + } +} diff --git a/app/Transformers/Api/Application/EggVariableTransformer.php b/app/Transformers/Api/Application/EggVariableTransformer.php new file mode 100644 index 000000000..decb038ab --- /dev/null +++ b/app/Transformers/Api/Application/EggVariableTransformer.php @@ -0,0 +1,24 @@ +toArray(); + } +} diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php new file mode 100644 index 000000000..d003053d6 --- /dev/null +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -0,0 +1,71 @@ +toArray(); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Location $location + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeServers(Location $location) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $location->loadMissing('servers'); + + return $this->collection($location->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Location $location + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeNodes(Location $location) + { + if (! $this->authorize(AdminAcl::RESOURCE_NODES)) { + return $this->null(); + } + + $location->loadMissing('nodes'); + + return $this->collection($location->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), 'node'); + } +} diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php new file mode 100644 index 000000000..9517af61d --- /dev/null +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -0,0 +1,63 @@ +toArray(); + + $response[$model->getUpdatedAtColumn()] = $this->formatTimestamp($model->updated_at); + $response[$model->getCreatedAtColumn()] = $this->formatTimestamp($model->created_at); + + return $response; + } + + /** + * Include the Eggs relationship on the given Nest model transformation. + * + * @param \Pterodactyl\Models\Nest $model + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeEggs(Nest $model) + { + if (! $this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + $model->loadMissing('eggs'); + + return $this->collection($model->getRelation('eggs'), $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + } +} diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php new file mode 100644 index 000000000..d47183fc7 --- /dev/null +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -0,0 +1,106 @@ +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[$node->getUpdatedAtColumn()] = $this->formatTimestamp($node->updated_at); + $response[$node->getCreatedAtColumn()] = $this->formatTimestamp($node->created_at); + + return $response; + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeAllocations(Node $node) + { + if (! $this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { + return $this->null(); + } + + $node->loadMissing('allocations'); + + return $this->collection( + $node->getRelation('allocations'), $this->makeTransformer(AllocationTransformer::class), 'allocation' + ); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeLocation(Node $node) + { + if (! $this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { + return $this->null(); + } + + $node->loadMissing('location'); + + return $this->item( + $node->getRelation('location'), $this->makeTransformer(LocationTransformer::class), 'location' + ); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeServers(Node $node) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $node->loadMissing('servers'); + + return $this->collection( + $node->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server' + ); + } +} diff --git a/app/Transformers/Admin/PackTransformer.php b/app/Transformers/Api/Application/PackTransformer.php similarity index 63% rename from app/Transformers/Admin/PackTransformer.php rename to app/Transformers/Api/Application/PackTransformer.php index 8d059faaf..973002ae8 100644 --- a/app/Transformers/Admin/PackTransformer.php +++ b/app/Transformers/Api/Application/PackTransformer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\Admin; @@ -50,8 +35,7 @@ class PackTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php new file mode 100644 index 000000000..c71627296 --- /dev/null +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -0,0 +1,103 @@ +encrypter = $encrypter; + } + + /** + * Return the resource name for the JSONAPI output. + * + * @return string + */ + public function getResourceName(): string + { + return Database::RESOURCE_NAME; + } + + /** + * Transform a database model in a representation for the application API. + * + * @param \Pterodactyl\Models\Database $model + * @return array + */ + public function transform(Database $model): array + { + return [ + 'id' => $model->id, + 'server' => $model->server_id, + 'host' => $model->database_host_id, + 'database' => $model->database, + 'username' => $model->username, + 'remote' => $model->remote, + 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->created_at) + ->setTimezone(config('app.timezone')) + ->toIso8601String(), + 'updated_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->updated_at) + ->setTimezone(config('app.timezone')) + ->toIso8601String(), + ]; + } + + /** + * Include the database password in the request. + * + * @param \Pterodactyl\Models\Database $model + * @return \League\Fractal\Resource\Item + */ + 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. + * + * @param \Pterodactyl\Models\Database $model + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeHost(Database $model) + { + if (! $this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { + return $this->null(); + } + + $model->loadMissing('host'); + + return $this->item( + $model->getRelation('host'), + $this->makeTransformer(DatabaseHostTransformer::class), + DatabaseHost::RESOURCE_NAME + ); + } +} diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php new file mode 100644 index 000000000..914449ef2 --- /dev/null +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -0,0 +1,247 @@ +environmentService = $environmentService; + } + + /** + * Return the resource name for the JSONAPI output. + * + * @return string + */ + public function getResourceName(): string + { + return Server::RESOURCE_NAME; + } + + /** + * Return a generic transformed server array. + * + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function transform(Server $server): array + { + return [ + 'id' => $server->getKey(), + 'uuid' => $server->uuid, + 'identifier' => $server->uuidShort, + 'name' => $server->name, + 'description' => $server->description, + 'suspended' => (bool) $server->suspended, + 'limits' => [ + 'memory' => $server->memory, + 'swap' => $server->swap, + 'disk' => $server->disk, + 'io' => $server->io, + 'cpu' => $server->cpu, + ], + 'user' => $server->owner_id, + 'node' => $server->node_id, + 'allocation' => $server->allocation_id, + 'nest' => $server->nest_id, + 'egg' => $server->egg_id, + 'pack' => $server->pack_id, + 'container' => [ + 'startup_command' => $server->startup, + 'image' => $server->image, + 'installed' => (int) $server->installed === 1, + 'environment' => $this->environmentService->handle($server), + ], + 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $server->created_at)->setTimezone('UTC')->toIso8601String(), + 'updated_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $server->updated_at)->setTimezone('UTC')->toIso8601String(), + ]; + } + + /** + * Return a generic array of allocations for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeAllocations(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_ALLOCATIONS)) { + return $this->null(); + } + + $server->loadMissing('allocations'); + + return $this->collection($server->getRelation('allocations'), $this->makeTransformer(AllocationTransformer::class), 'allocation'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeSubusers(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_USERS)) { + return $this->null(); + } + + $server->loadMissing('subusers'); + + return $this->collection($server->getRelation('subusers'), $this->makeTransformer(UserTransformer::class), 'user'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeUser(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_USERS)) { + return $this->null(); + } + + $server->loadMissing('user'); + + return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + } + + /** + * Return a generic array with pack information for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ +// public function includePack(Server $server) +// { +// if (! $this->authorize(AdminAcl::RESOURCE_PACKS)) { +// return $this->null(); +// } +// +// $server->loadMissing('pack'); +// +// return $this->item($server->getRelation('pack'), $this->makeTransformer(PackTransformer::class), 'pack'); +// } + + /** + * Return a generic array with nest information for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ +// public function includeNest(Server $server) +// { +// if (! $this->authorize(AdminAcl::RESOURCE_NESTS)) { +// return $this->null(); +// } +// +// $server->loadMissing('nest'); +// +// return $this->item($server->getRelation('nest'), $this->makeTransformer(NestTransformer::class), 'nest'); +// } + + /** + * Return a generic array with service option information for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeOption(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + $server->loadMissing('egg'); + + return $this->item($server->getRelation('egg'), $this->makeTransformer(EggVariableTransformer::class), 'egg'); + } + + /** + * Return a generic array of data about subusers for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeVariables(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $server->loadMissing('variables'); + + return $this->collection($server->getRelation('variables'), $this->makeTransformer(ServerVariableTransformer::class), 'server_variable'); + } + + /** + * Return a generic array with pack information for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeLocation(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_LOCATIONS)) { + return $this->null(); + } + + $server->loadMissing('location'); + + return $this->item($server->getRelation('location'), $this->makeTransformer(LocationTransformer::class), 'location'); + } + + /** + * Return a generic array with pack information for this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeNode(Server $server) + { + if (! $this->authorize(AdminAcl::RESOURCE_NODES)) { + return $this->null(); + } + + $server->loadMissing('node'); + + return $this->item($server->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node'); + } +} diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php new file mode 100644 index 000000000..5ce592b5f --- /dev/null +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -0,0 +1,54 @@ +toArray(); + } + + /** + * Return the parent service variable data. + * + * @param \Pterodactyl\Models\ServerVariable $variable + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includeParent(ServerVariable $variable) + { + if (! $this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + $variable->loadMissing('variable'); + + return $this->item($variable->getRelation('variable'), $this->makeTransformer(EggVariableTransformer::class), 'variable'); + } +} diff --git a/app/Transformers/Admin/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php similarity index 55% rename from app/Transformers/Admin/SubuserTransformer.php rename to app/Transformers/Api/Application/SubuserTransformer.php index 129da7ad3..93ed25d52 100644 --- a/app/Transformers/Admin/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -3,30 +3,14 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; use League\Fractal\TransformerAbstract; class SubuserTransformer extends TransformerAbstract @@ -41,8 +25,7 @@ class SubuserTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php new file mode 100644 index 000000000..56749deaa --- /dev/null +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -0,0 +1,67 @@ + $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), + ]; + } + + /** + * Return the servers associated with this user. + * + * @param \Pterodactyl\Models\User $user + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeServers(User $user) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $user->loadMissing('servers'); + + return $this->collection($user->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + } +} diff --git a/app/Transformers/Daemon/ApiKeyTransformer.php b/app/Transformers/Daemon/ApiKeyTransformer.php new file mode 100644 index 000000000..54aa37d6f --- /dev/null +++ b/app/Transformers/Daemon/ApiKeyTransformer.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Transformers\Daemon; + +use Carbon\Carbon; +use Pterodactyl\Models\DaemonKey; +use Pterodactyl\Models\Permission; +use League\Fractal\TransformerAbstract; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class ApiKeyTransformer extends TransformerAbstract +{ + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface + */ + private $keyRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + private $repository; + + /** + * ApiKeyTransformer constructor. + * + * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $keyRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + */ + public function __construct(DaemonKeyRepositoryInterface $keyRepository, SubuserRepositoryInterface $repository) + { + $this->repository = $repository; + $this->keyRepository = $keyRepository; + } + + /** + * Return a listing of servers that a daemon key can access. + * + * @param \Pterodactyl\Models\DaemonKey $key + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function transform(DaemonKey $key) + { + $this->keyRepository->loadServerAndUserRelations($key); + + if ($key->user_id === $key->getRelation('server')->owner_id || $key->getRelation('user')->root_admin) { + return [ + 'id' => $key->getRelation('server')->uuid, + 'is_temporary' => true, + 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), + 'permissions' => ['s:*'], + ]; + } + + $subuser = $this->repository->getWithPermissionsUsingUserAndServer($key->user_id, $key->server_id); + + $permissions = $subuser->getRelation('permissions')->pluck('permission')->toArray(); + $mappings = Permission::getPermissions(true); + $daemonPermissions = ['s:console']; + + foreach ($permissions as $permission) { + if (! is_null($mappings[$permission])) { + $daemonPermissions[] = $mappings[$permission]; + } + } + + return [ + 'id' => $key->getRelation('server')->uuid, + 'is_temporary' => true, + 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), + 'permissions' => $daemonPermissions, + ]; + } +} diff --git a/app/Transformers/User/AllocationTransformer.php b/app/Transformers/User/AllocationTransformer.php index 0fd0be453..8ea0c8f91 100644 --- a/app/Transformers/User/AllocationTransformer.php +++ b/app/Transformers/User/AllocationTransformer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\User; @@ -39,8 +24,6 @@ class AllocationTransformer extends TransformerAbstract /** * Setup allocation transformer with access to server data. - * - * @return void */ public function __construct(Server $server) { diff --git a/app/Transformers/User/OverviewTransformer.php b/app/Transformers/User/OverviewTransformer.php index c8e1db9ed..b59990766 100644 --- a/app/Transformers/User/OverviewTransformer.php +++ b/app/Transformers/User/OverviewTransformer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\User; diff --git a/app/Transformers/User/ServerTransformer.php b/app/Transformers/User/ServerTransformer.php index 4e5aacb56..031ae82f8 100644 --- a/app/Transformers/User/ServerTransformer.php +++ b/app/Transformers/User/ServerTransformer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\User; diff --git a/app/Transformers/User/StatsTransformer.php b/app/Transformers/User/StatsTransformer.php index 6b08ea8a5..2a2e1d5ec 100644 --- a/app/Transformers/User/StatsTransformer.php +++ b/app/Transformers/User/StatsTransformer.php @@ -3,23 +3,8 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\User; diff --git a/app/Transformers/User/SubuserTransformer.php b/app/Transformers/User/SubuserTransformer.php index e1a122f1f..faac5965c 100644 --- a/app/Transformers/User/SubuserTransformer.php +++ b/app/Transformers/User/SubuserTransformer.php @@ -3,29 +3,13 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ namespace Pterodactyl\Transformers\User; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; use League\Fractal\TransformerAbstract; class SubuserTransformer extends TransformerAbstract diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 000000000..bfb12cc7a --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,73 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ +if (! function_exists('human_readable')) { + /** + * Generate a human-readable filesize for a given file path. + * + * @param string $path + * @param int $precision + * @return string + */ + function human_readable($path, $precision = 2) + { + if (is_numeric($path)) { + $i = 0; + while (($path / 1024) > 0.9) { + $path = $path / 1024; + $i++; + } + + return round($path, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; + } + + return app('file')->humanReadableSize($path, $precision); + } +} + +if (! function_exists('is_digit')) { + /** + * 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) + { + return is_bool($value) ? false : ctype_digit(strval($value)); + } +} + +if (! function_exists('object_get_strict')) { + /** + * 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) + { + if (is_null($key) || trim($key) == '') { + return $object; + } + + foreach (explode('.', $key) as $segment) { + if (! is_object($object) || ! property_exists($object, $segment)) { + return value($default); + } + + $object = $object->{$segment}; + } + + return $object; + } +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..0bcc79196 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + target: 35 + threshold: 1 +comment: + layout: "diff" diff --git a/composer.json b/composer.json index 3eb02fb85..6f2f7c560 100644 --- a/composer.json +++ b/composer.json @@ -11,44 +11,54 @@ } ], "require": { - "php": ">=7.0.0", - "aws/aws-sdk-php": "3.29.7", - "barryvdh/laravel-debugbar": "2.4.0", - "daneeveritt/login-notifications": "1.0.0", - "doctrine/dbal": "2.5.12", - "edvinaskrucas/settings": "2.0.0", + "php": ">=7.2", "ext-mbstring": "*", - "ext-zip": "*", "ext-pdo_mysql": "*", - "fideloper/proxy": "3.3.3", - "guzzlehttp/guzzle": "6.2.3", - "igaster/laravel-theme": "1.16.0", - "laracasts/utilities": "2.1.0", - "laravel/framework": "5.4.27", - "laravel/tinker": "1.0.1", - "lord/laroute": "2.4.4", - "mtdowling/cron-expression": "1.2.0", - "nesbot/carbon": "1.22.1", - "nicolaslopezj/searchable": "1.9.5", - "pragmarx/google2fa": "1.0.1", - "predis/predis": "1.1.1", - "prologue/alerts": "0.4.1", - "s1lentium/iptools": "1.1.0", - "spatie/laravel-fractal": "4.0.1", - "webpatser/laravel-uuid": "2.0.1" + "ext-zip": "*", + "appstract/laravel-blade-directives": "^0.7", + "aws/aws-sdk-php": "^3.48", + "cakephp/chronos": "^1.1", + "doctrine/dbal": "^2.5", + "fideloper/proxy": "^3.3", + "guzzlehttp/guzzle": "^6.3", + "hashids/hashids": "^2.0", + "igaster/laravel-theme": "^1.16", + "laracasts/utilities": "^3.0", + "laravel/framework": "5.5.*", + "laravel/tinker": "^1.0", + "lord/laroute": "^2.4", + "matriphe/iso-639": "^1.2", + "mtdowling/cron-expression": "^1.2", + "nesbot/carbon": "^1.22", + "pragmarx/google2fa": "^2.0", + "predis/predis": "^1.1", + "prologue/alerts": "^0.4", + "ramsey/uuid": "^3.7", + "s1lentium/iptools": "^1.1", + "sofa/eloquence-base": "v5.5", + "sofa/eloquence-validable": "v5.5", + "spatie/laravel-fractal": "^5.3", + "webmozart/assert": "^1.2", + "znck/belongs-to-through": "^2.3" }, "require-dev": { - "barryvdh/laravel-ide-helper": "^2.3", - "friendsofphp/php-cs-fixer": "1.*", - "fzaninotto/faker": "~1.4", - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~5.7", - "sllh/php-cs-fixer-styleci-bridge": "^2.1" + "barryvdh/laravel-debugbar": "^3.1", + "barryvdh/laravel-ide-helper": "^2.4", + "codedungeon/phpunit-result-printer": "^0.6.0", + "filp/whoops": "^2.1", + "friendsofphp/php-cs-fixer": "^2.8.0", + "fzaninotto/faker": "^1.6", + "mockery/mockery": "^1.0", + "php-mock/php-mock-phpunit": "^2.0", + "phpunit/phpunit": "~6.4.0" }, "autoload": { "classmap": [ "database" ], + "files": [ + "app/helpers.php" + ], "psr-4": { "Pterodactyl\\": "app/" } @@ -59,21 +69,15 @@ } }, "scripts": { - "pre-install-cmd": [ - "rm -f bootstrap/cache/services.php bootstrap/cache/compiled.php" + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], - "pre-update-cmd": [ - "rm -f bootstrap/cache/services.php bootstrap/cache/compiled.php" + "post-create-project-cmd": [ + "@php artisan key:generate" ], - "post-install-cmd": [ - "Illuminate\\Foundation\\ComposerScripts::postInstall", - "php artisan optimize", - "php artisan config:cache" - ], - "post-update-cmd": [ - "Illuminate\\Foundation\\ComposerScripts::postUpdate", - "php artisan optimize", - "php artisan config:cache" + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover" ] }, "prefer-stable": true, diff --git a/composer.lock b/composer.lock index 4fcf88be1..9208cdfdf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,23 +4,80 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c82fadb556adb7d7f4405c35dee2ef64", + "content-hash": "86cf8a899601833098c86cb505764e92", "packages": [ { - "name": "aws/aws-sdk-php", - "version": "3.29.7", + "name": "appstract/laravel-blade-directives", + "version": "0.7.1", "source": { "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "76540001ff938c072db5367a7c945296984b999b" + "url": "https://github.com/appstract/laravel-blade-directives.git", + "reference": "398d36a1c1f2740c81358b99473fd1564a81c406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/76540001ff938c072db5367a7c945296984b999b", - "reference": "76540001ff938c072db5367a7c945296984b999b", + "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/398d36a1c1f2740c81358b99473fd1564a81c406", + "reference": "398d36a1c1f2740c81358b99473fd1564a81c406", "shasum": "" }, "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Appstract\\BladeDirectives\\BladeDirectivesServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Appstract\\BladeDirectives\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gijs Jorissen", + "email": "hello@appstract.team", + "homepage": "https://appstract.team", + "role": "Developer" + } + ], + "description": "Handy Blade directives", + "homepage": "https://github.com/appstract/laravel-blade-directives", + "keywords": [ + "appstract", + "laravel-blade-directives" + ], + "time": "2018-01-08T13:58:34+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.52.6", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c9af7657eddc0267cc7ac4f969c10d5c18459992", + "reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", "guzzlehttp/guzzle": "^5.3.1|^6.2.1", "guzzlehttp/promises": "~1.0", "guzzlehttp/psr7": "^1.4.1", @@ -33,13 +90,9 @@ "behat/behat": "~3.0", "doctrine/cache": "~1.4", "ext-dom": "*", - "ext-json": "*", "ext-openssl": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "ext-spl": "*", "nette/neon": "^2.3", - "phpunit/phpunit": "^4.8.35|^5.4.0", + "phpunit/phpunit": "^4.8.35|^5.4.3", "psr/cache": "^1.0" }, "suggest": { @@ -84,48 +137,39 @@ "s3", "sdk" ], - "time": "2017-06-16T17:29:33+00:00" + "time": "2018-02-09T22:53:37+00:00" }, { - "name": "barryvdh/laravel-debugbar", - "version": "v2.4.0", + "name": "cakephp/chronos", + "version": "1.1.4", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763" + "url": "https://github.com/cakephp/chronos.git", + "reference": "85bcaea6a832684b32ef54b2487b0c14a172e9e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/de15d00a74696db62e1b4782474c27ed0c4fc763", - "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763", + "url": "https://api.github.com/repos/cakephp/chronos/zipball/85bcaea6a832684b32ef54b2487b0c14a172e9e6", + "reference": "85bcaea6a832684b32ef54b2487b0c14a172e9e6", "shasum": "" }, "require": { - "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", - "maximebf/debugbar": "~1.13.0", - "php": ">=5.5.9", - "symfony/finder": "~2.7|~3.0" + "php": "^5.5.9|^7" + }, + "require-dev": { + "athletic/athletic": "~0.1", + "cakephp/cakephp-codesniffer": "~2.3", + "phpbench/phpbench": "@dev", + "phpstan/phpstan": "^0.6.4", + "phpunit/phpunit": "<6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, - "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], - "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facade" - } - } - }, "autoload": { "psr-4": { - "Barryvdh\\Debugbar\\": "src/" + "Cake\\Chronos\\": "src" }, "files": [ - "src/helpers.php" + "src/carbon_compat.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -134,118 +178,23 @@ ], "authors": [ { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], - "time": "2017-06-01T17:46:08+00:00" - }, - { - "name": "christian-riesen/base32", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/ChristianRiesen/base32.git", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "0.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Base32\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + }, { - "name": "Christian Riesen", - "email": "chris.riesen@gmail.com", - "homepage": "http://christianriesen.com", - "role": "Developer" + "name": "The CakePHP Team", + "homepage": "http://cakephp.org" } ], - "description": "Base32 encoder/decoder according to RFC 4648", - "homepage": "https://github.com/ChristianRiesen/base32", + "description": "A simple API extension for DateTime.", + "homepage": "http://cakephp.org", "keywords": [ - "base32", - "decode", - "encode", - "rfc4648" + "date", + "datetime", + "time" ], - "time": "2016-05-05T11:49:03+00:00" - }, - { - "name": "daneeveritt/login-notifications", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/DaneEveritt/login-notifications.git", - "reference": "a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/DaneEveritt/login-notifications/zipball/a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910", - "reference": "a12e9b25e9a5e42d3f25c16579ba6dc2b8aba910", - "shasum": "" - }, - "require": { - "laravel/framework": "~5.3.0|~5.4.0", - "nesbot/carbon": "1.22.*", - "php": "^5.6|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "DaneEveritt\\LoginNotifications\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dane Everitt", - "email": "dane@daneeveritt.com" - } - ], - "description": "Login notifications for Laravel", - "keywords": [ - "email", - "events", - "laravel", - "login", - "notifications" - ], - "time": "2017-04-14T20:57:26+00:00" + "time": "2018-01-13T12:19:50+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -282,30 +231,30 @@ }, { "name": "doctrine/annotations", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" + "php": "^7.1" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -346,37 +295,41 @@ "docblock", "parser" ], - "time": "2017-02-24T16:22:25+00:00" + "time": "2017-12-06T07:11:42+00:00" }, { "name": "doctrine/cache", - "version": "v1.6.1", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", - "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", "shasum": "" }, "require": { - "php": "~5.5|~7.0" + "php": "~7.1" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0", - "predis/predis": "~1.0", - "satooshi/php-coveralls": "~0.6" + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -416,24 +369,24 @@ "cache", "caching" ], - "time": "2016-10-29T11:16:17+00:00" + "time": "2017-08-25T07:02:50+00:00" }, { "name": "doctrine/collections", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" }, "require-dev": { "doctrine/coding-standard": "~0.1@dev", @@ -483,20 +436,20 @@ "collections", "iterator" ], - "time": "2017-01-03T10:49:41+00:00" + "time": "2017-07-22T10:37:32+00:00" }, { "name": "doctrine/common", - "version": "v2.7.2", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "930297026c8009a567ac051fd545bf6124150347" + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", - "reference": "930297026c8009a567ac051fd545bf6124150347", + "url": "https://api.github.com/repos/doctrine/common/zipball/f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", "shasum": "" }, "require": { @@ -505,15 +458,15 @@ "doctrine/collections": "1.*", "doctrine/inflector": "1.*", "doctrine/lexer": "1.*", - "php": "~5.6|~7.0" + "php": "~7.1" }, "require-dev": { - "phpunit/phpunit": "^5.4.6" + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev" + "dev-master": "2.8.x-dev" } }, "autoload": { @@ -556,28 +509,30 @@ "persistence", "spl" ], - "time": "2017-01-13T14:02:13+00:00" + "time": "2017-08-31T08:43:38+00:00" }, { "name": "doctrine/dbal", - "version": "v2.5.12", + "version": "v2.6.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" + "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", - "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/e3eed9b1facbb0ced3a0995244843a189e7d1b13", + "reference": "e3eed9b1facbb0ced3a0995244843a189e7d1b13", "shasum": "" }, "require": { - "doctrine/common": ">=2.4,<2.8-dev", - "php": ">=5.3.2" + "doctrine/common": "^2.7.1", + "ext-pdo": "*", + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "4.*", + "phpunit/phpunit": "^5.4.6", + "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", "symfony/console": "2.*||^3.0" }, "suggest": { @@ -589,7 +544,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5.x-dev" + "dev-master": "2.6.x-dev" } }, "autoload": { @@ -627,37 +582,37 @@ "persistence", "queryobject" ], - "time": "2017-02-08T12:53:47+00:00" + "time": "2017-11-19T13:38:54+00:00" }, { "name": "doctrine/inflector", - "version": "v1.1.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -694,7 +649,7 @@ "singularize", "string" ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2018-01-09T20:05:19+00:00" }, { "name": "doctrine/lexer", @@ -751,37 +706,41 @@ "time": "2014-09-09T13:34:57+00:00" }, { - "name": "edvinaskrucas/settings", - "version": "2.0.0", + "name": "egulias/email-validator", + "version": "2.1.3", "source": { "type": "git", - "url": "https://github.com/edvinaskrucas/settings.git", - "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022" + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/edvinaskrucas/settings/zipball/23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", - "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/1bec00a10039b823cc94eef4eddd47dcd3b2ca04", + "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04", "shasum": "" }, "require": { - "illuminate/console": "^5.2", - "illuminate/database": "^5.2", - "illuminate/filesystem": "^5.2", - "illuminate/support": "^5.2", - "php": "^5.5|^7.0" + "doctrine/lexer": "^1.0.1", + "php": ">= 5.5" }, "require-dev": { - "mockery/mockery": "0.9.*" + "dominicsayers/isemail": "dev-master", + "phpunit/phpunit": "^4.8.35", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Krucas\\Settings\\": "src/" - }, - "files": [ - "src/helpers.php" - ] + "psr-4": { + "Egulias\\EmailValidator\\": "EmailValidator" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -789,35 +748,40 @@ ], "authors": [ { - "name": "Edvinas Kručas", - "email": "edv.krucas@gmail.com" + "name": "Eduardo Gulias Davis" } ], - "description": "Persistent settings package for Laravel framework.", + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", "keywords": [ - "Settings", - "laravel", - "persistent settings" + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" ], - "time": "2016-01-19T13:50:39+00:00" + "time": "2017-11-15T23:40:40+00:00" }, { "name": "erusev/parsedown", - "version": "1.6.2", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01" + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01", - "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548", + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548", "shasum": "" }, "require": { "php": ">=5.3.0" }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, "type": "library", "autoload": { "psr-0": { @@ -841,20 +805,20 @@ "markdown", "parser" ], - "time": "2017-03-29T16:04:15+00:00" + "time": "2017-11-14T20:44:03+00:00" }, { "name": "fideloper/proxy", - "version": "3.3.3", + "version": "3.3.4", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477" + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/985eac8f966c03b4d9503cad9b5e5a51d41ce477", - "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9cdf6f118af58d89764249bbcc7bb260c132924f", + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f", "shasum": "" }, "require": { @@ -870,6 +834,11 @@ "extra": { "branch-alias": { "dev-master": "3.3-dev" + }, + "laravel": { + "providers": [ + "Fideloper\\Proxy\\TrustedProxyServiceProvider" + ] } }, "autoload": { @@ -893,20 +862,20 @@ "proxy", "trusted proxy" ], - "time": "2017-05-31T12:50:41+00:00" + "time": "2017-06-15T17:19:42+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.2.3", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006" + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006", - "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", "shasum": "" }, "require": { @@ -916,9 +885,12 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.0", + "phpunit/phpunit": "^4.0 || ^5.0", "psr/log": "^1.0" }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, "type": "library", "extra": { "branch-alias": { @@ -955,7 +927,7 @@ "rest", "web service" ], - "time": "2017-02-28T22:50:30+00:00" + "time": "2017-06-22T18:50:49+00:00" }, { "name": "guzzlehttp/promises", @@ -1073,6 +1045,72 @@ ], "time": "2017-03-20T17:10:46+00:00" }, + { + "name": "hashids/hashids", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/ivanakimov/hashids.php.git", + "reference": "7a945a5192d4a5c8888364970feece9bc26179df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ivanakimov/hashids.php/zipball/7a945a5192d4a5c8888364970feece9bc26179df", + "reference": "7a945a5192d4a5c8888364970feece9bc26179df", + "shasum": "" + }, + "require": { + "php": "^5.6.4 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.3" + }, + "suggest": { + "ext-bcmatch": "Required to use BC Math arbitrary precision mathematics (*).", + "ext-gmp": "Required to use GNU multiple precision mathematics (*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Hashids\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Akimov", + "email": "ivan@barreleye.com", + "homepage": "https://twitter.com/IvanAkimov" + }, + { + "name": "Vincent Klaiber", + "email": "hello@vinkla.com", + "homepage": "https://vinkla.com" + } + ], + "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers", + "homepage": "http://hashids.org/php", + "keywords": [ + "bitly", + "decode", + "encode", + "hash", + "hashid", + "hashids", + "ids", + "obfuscate", + "youtube" + ], + "time": "2017-10-28T11:24:20+00:00" + }, { "name": "igaster/laravel-theme", "version": "v1.16", @@ -1218,16 +1256,16 @@ }, { "name": "laracasts/utilities", - "version": "2.1", + "version": "3.0", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378" + "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4402a0ed774f8eb36ea7ba169341d9d5b6049378", - "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/298fb3c6f29901a4550c4f98b57c05f368341d04", + "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04", "shasum": "" }, "require": { @@ -1238,7 +1276,20 @@ "phpspec/phpspec": "~2.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laracasts\\Utilities\\JavaScript\\JavaScriptServiceProvider" + ], + "aliases": { + "JavaScript": "Laracasts\\Utilities\\JavaScript\\JavaScriptFacade" + } + } + }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { "Laracasts\\Utilities\\JavaScript\\": "src/" } @@ -1258,43 +1309,44 @@ "javascript", "laravel" ], - "time": "2015-10-01T05:16:28+00:00" + "time": "2017-09-01T17:25:57+00:00" }, { "name": "laravel/framework", - "version": "v5.4.27", + "version": "v5.5.34", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404" + "reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/66f5e1b37cbd66e730ea18850ded6dc0ad570404", - "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404", + "url": "https://api.github.com/repos/laravel/framework/zipball/1de7c0aec13eadbdddc2d1ba4019b064b2c6b966", + "reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966", "shasum": "" }, "require": { - "doctrine/inflector": "~1.0", + "doctrine/inflector": "~1.1", "erusev/parsedown": "~1.6", "ext-mbstring": "*", "ext-openssl": "*", "league/flysystem": "~1.0", - "monolog/monolog": "~1.11", + "monolog/monolog": "~1.12", "mtdowling/cron-expression": "~1.0", "nesbot/carbon": "~1.20", - "paragonie/random_compat": "~1.4|~2.0", - "php": ">=5.6.4", + "php": ">=7.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", "ramsey/uuid": "~3.0", - "swiftmailer/swiftmailer": "~5.4", - "symfony/console": "~3.2", - "symfony/debug": "~3.2", - "symfony/finder": "~3.2", - "symfony/http-foundation": "~3.2", - "symfony/http-kernel": "~3.2", - "symfony/process": "~3.2", - "symfony/routing": "~3.2", - "symfony/var-dumper": "~3.2", + "swiftmailer/swiftmailer": "~6.0", + "symfony/console": "~3.3", + "symfony/debug": "~3.3", + "symfony/finder": "~3.3", + "symfony/http-foundation": "~3.3", + "symfony/http-kernel": "~3.3", + "symfony/process": "~3.3", + "symfony/routing": "~3.3", + "symfony/var-dumper": "~3.3", "tijsverkoyen/css-to-inline-styles": "~2.2", "vlucas/phpdotenv": "~2.2" }, @@ -1311,7 +1363,6 @@ "illuminate/database": "self.version", "illuminate/encryption": "self.version", "illuminate/events": "self.version", - "illuminate/exception": "self.version", "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", @@ -1333,33 +1384,38 @@ "require-dev": { "aws/aws-sdk-php": "~3.0", "doctrine/dbal": "~2.5", - "mockery/mockery": "~0.9.4", + "filp/whoops": "^2.1.4", + "mockery/mockery": "~1.0", + "orchestra/testbench-core": "3.5.*", "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~5.7", - "predis/predis": "~1.0", - "symfony/css-selector": "~3.2", - "symfony/dom-crawler": "~3.2" + "phpunit/phpunit": "~6.0", + "predis/predis": "^1.1.1", + "symfony/css-selector": "~3.3", + "symfony/dom-crawler": "~3.3" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", "laravel/tinker": "Required to use the tinker console command (~1.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).", "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", "nexmo/client": "Required to use the Nexmo transport (~1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.2).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.2).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.5-dev" } }, "autoload": { @@ -1387,20 +1443,20 @@ "framework", "laravel" ], - "time": "2017-06-15T19:08:25+00:00" + "time": "2018-02-06T15:36:55+00:00" }, { "name": "laravel/tinker", - "version": "v1.0.1", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "7eb2e281395131897407285672ef5532e87e17f9" + "reference": "852c2abe0b0991555a403f1c0583e64de6acb4a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/7eb2e281395131897407285672ef5532e87e17f9", - "reference": "7eb2e281395131897407285672ef5532e87e17f9", + "url": "https://api.github.com/repos/laravel/tinker/zipball/852c2abe0b0991555a403f1c0583e64de6acb4a6", + "reference": "852c2abe0b0991555a403f1c0583e64de6acb4a6", "shasum": "" }, "require": { @@ -1409,7 +1465,7 @@ "illuminate/support": "~5.1", "php": ">=5.5.9", "psy/psysh": "0.7.*|0.8.*", - "symfony/var-dumper": "~3.0" + "symfony/var-dumper": "~3.0|~4.0" }, "require-dev": { "phpunit/phpunit": "~4.0|~5.0" @@ -1450,20 +1506,20 @@ "laravel", "psysh" ], - "time": "2017-06-01T16:31:26+00:00" + "time": "2017-12-18T16:25:11+00:00" }, { "name": "league/flysystem", - "version": "1.0.40", + "version": "1.0.42", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61" + "reference": "09eabc54e199950041aef258a85847676496fe8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61", - "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/09eabc54e199950041aef258a85847676496fe8e", + "reference": "09eabc54e199950041aef258a85847676496fe8e", "shasum": "" }, "require": { @@ -1474,23 +1530,24 @@ }, "require-dev": { "ext-fileinfo": "*", - "mockery/mockery": "~0.9", - "phpspec/phpspec": "^2.2", - "phpunit/phpunit": "~4.8" + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7" }, "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", "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-copy": "Allows you to use Copy.com storage", "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" + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" }, "type": "library", "extra": { @@ -1533,20 +1590,20 @@ "sftp", "storage" ], - "time": "2017-04-28T10:15:08+00:00" + "time": "2018-01-27T16:03:56+00:00" }, { "name": "league/fractal", - "version": "0.16.0", + "version": "0.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "d0445305e308d9207430680acfd580557b679ddc" + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/d0445305e308d9207430680acfd580557b679ddc", - "reference": "d0445305e308d9207430680acfd580557b679ddc", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e", "shasum": "" }, "require": { @@ -1597,28 +1654,28 @@ "league", "rest" ], - "time": "2017-03-12T01:28:43+00:00" + "time": "2017-06-12T11:04:56+00:00" }, { "name": "lord/laroute", - "version": "v2.4.4", + "version": "2.4.7", "source": { "type": "git", "url": "https://github.com/aaronlord/laroute.git", - "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e" + "reference": "915501506ee5dfd07b9f9716537a16e1c66d4125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aaronlord/laroute/zipball/2adee9daa5491f1ad7b953fc01df36ebc7294c3e", - "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e", + "url": "https://api.github.com/repos/aaronlord/laroute/zipball/915501506ee5dfd07b9f9716537a16e1c66d4125", + "reference": "915501506ee5dfd07b9f9716537a16e1c66d4125", "shasum": "" }, "require": { - "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", "php": ">=5.4.0" }, "require-dev": { @@ -1648,44 +1705,29 @@ "routes", "routing" ], - "time": "2017-02-08T11:05:52+00:00" + "time": "2018-02-10T01:17:07+00:00" }, { - "name": "maximebf/debugbar", - "version": "1.13.1", + "name": "matriphe/iso-639", + "version": "1.2", "source": { "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a" + "url": "https://github.com/matriphe/php-iso-639.git", + "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/afee79a236348e39a44cb837106b7c5b4897ac2a", - "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a", + "url": "https://api.github.com/repos/matriphe/php-iso-639/zipball/0245d844daeefdd22a54b47103ffdb0e03c323e1", + "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1", "shasum": "" }, - "require": { - "php": ">=5.3.0", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0" - }, "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" + "phpunit/phpunit": "^4.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.13-dev" - } - }, "autoload": { "psr-4": { - "DebugBar\\": "src/DebugBar/" + "Matriphe\\ISO639\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1694,35 +1736,33 @@ ], "authors": [ { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" + "name": "Muhammad Zamroni", + "email": "halo@matriphe.com" } ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", + "description": "PHP library to convert ISO-639-1 code to language name.", "keywords": [ - "debug", - "debugbar" + "639", + "iso", + "iso-639", + "lang", + "language", + "laravel" ], - "time": "2017-01-05T08:46:19+00:00" + "time": "2017-07-19T15:11:19+00:00" }, { "name": "monolog/monolog", - "version": "1.22.1", + "version": "1.23.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", "shasum": "" }, "require": { @@ -1743,7 +1783,7 @@ "phpunit/phpunit-mock-objects": "2.3.0", "ruflin/elastica": ">=0.90 <3.0", "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -1787,11 +1827,11 @@ "logging", "psr-3" ], - "time": "2017-03-13T07:08:03+00:00" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "mtdowling/cron-expression", - "version": "v1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/mtdowling/cron-expression.git", @@ -1941,64 +1981,18 @@ ], "time": "2017-01-16T07:55:07+00:00" }, - { - "name": "nicolaslopezj/searchable", - "version": "1.9.5", - "source": { - "type": "git", - "url": "https://github.com/nicolaslopezj/searchable.git", - "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nicolaslopezj/searchable/zipball/1351b1b21ae9be9e0f49f375f56488df839723d4", - "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "illuminate/database": "4.2.x|~5.0", - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Nicolaslopezj\\Searchable\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Lopez", - "email": "nicolaslopezj@me.com" - } - ], - "description": "Eloquent model search trait.", - "keywords": [ - "database", - "eloquent", - "laravel", - "model", - "search", - "searchable" - ], - "time": "2016-12-16T21:23:34+00:00" - }, { "name": "nikic/php-parser", - "version": "v3.0.5", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d" + "reference": "e57b3a09784f846411aa7ed664eedb73e3399078" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2b9e2f71b722f7c53918ab0c25f7646c2013f17d", - "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/e57b3a09784f846411aa7ed664eedb73e3399078", + "reference": "e57b3a09784f846411aa7ed664eedb73e3399078", "shasum": "" }, "require": { @@ -2036,20 +2030,82 @@ "parser", "php" ], - "time": "2017-03-05T18:23:57+00:00" + "time": "2018-01-25T21:31:33+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.10", + "name": "paragonie/constant_time_encoding", + "version": "v2.2.1", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "7c74c5d08761ead7b5e89d07c854bc28eb0b2186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/7c74c5d08761ead7b5e89d07c854bc28eb0b2186", + "reference": "7c74c5d08761ead7b5e89d07c854bc28eb0b2186", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2018-01-23T00:54:57+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", "shasum": "" }, "require": { @@ -2084,30 +2140,31 @@ "pseudorandom", "random" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-09-27T21:40:39+00:00" }, { "name": "pragmarx/google2fa", - "version": "v1.0.1", + "version": "v2.0.7", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" + "reference": "5a818bda62fab0c0a79060b06d50d50b5525d631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/5a818bda62fab0c0a79060b06d50d50b5525d631", + "reference": "5a818bda62fab0c0a79060b06d50d50b5525d631", "shasum": "" }, "require": { - "christian-riesen/base32": "~1.3", + "paragonie/constant_time_encoding": "~1.0|~2.0", "paragonie/random_compat": "~1.4|~2.0", "php": ">=5.4", "symfony/polyfill-php56": "~1.2" }, "require-dev": { - "phpspec/phpspec": "~2.1" + "bacon/bacon-qr-code": "~1.0", + "phpunit/phpunit": "~4|~5|~6" }, "suggest": { "bacon/bacon-qr-code": "Required to generate inline QR Codes." @@ -2115,16 +2172,14 @@ "type": "library", "extra": { "component": "package", - "frameworks": [ - "Laravel" - ], "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { "psr-4": { - "PragmaRX\\Google2FA\\": "src/" + "PragmaRX\\Google2FA\\": "src/", + "PragmaRX\\Google2FA\\Tests\\": "tests/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2140,12 +2195,13 @@ ], "description": "A One Time Password Authentication package, compatible with Google Authenticator.", "keywords": [ + "2fa", "Authentication", "Two Factor Authentication", "google2fa", "laravel" ], - "time": "2016-07-18T20:25:04+00:00" + "time": "2018-01-06T16:21:07+00:00" }, { "name": "predis/predis", @@ -2247,6 +2303,55 @@ ], "time": "2017-01-24T13:22:25+00:00" }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2345,17 +2450,65 @@ "time": "2016-10-10T12:19:37+00:00" }, { - "name": "psy/psysh", - "version": "v0.8.6", + "name": "psr/simple-cache", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7028d6d525fb183d50b249b7c07598e3d386b27d", - "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24", + "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-01-02T13:31:39+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.8.17", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "5069b70e8c4ea492c2b5939b6eddc78bfe41cfec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/5069b70e8c4ea492c2b5939b6eddc78bfe41cfec", + "reference": "5069b70e8c4ea492c2b5939b6eddc78bfe41cfec", "shasum": "" }, "require": { @@ -2363,14 +2516,13 @@ "jakub-onderka/php-console-highlighter": "0.3.*", "nikic/php-parser": "~1.3|~2.0|~3.0", "php": ">=5.3.9", - "symfony/console": "~2.3.10|^2.4.2|~3.0", - "symfony/var-dumper": "~2.7|~3.0" + "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0", + "symfony/var-dumper": "~2.7|~3.0|~4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", "hoa/console": "~3.16|~1.14", - "phpunit/phpunit": "~4.4|~5.0", - "symfony/finder": "~2.1|~3.0" + "phpunit/phpunit": "^4.8.35|^5.4.3", + "symfony/finder": "~2.1|~3.0|~4.0" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", @@ -2415,20 +2567,20 @@ "interactive", "shell" ], - "time": "2017-06-04T10:34:20+00:00" + "time": "2017-12-28T16:14:16+00:00" }, { "name": "ramsey/uuid", - "version": "3.6.1", + "version": "3.7.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e" + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", - "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", "shasum": "" }, "require": { @@ -2439,17 +2591,15 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "apigen/apigen": "^4.1", - "codeception/aspect-mock": "^1.0 | ^2.0", + "codeception/aspect-mock": "^1.0 | ~2.0.0", "doctrine/annotations": "~1.2.0", "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", "ircmaxell/random-lib": "^1.1", "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.4", + "mockery/mockery": "^0.9.9", "moontoast/math": "^1.1", "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|>=5.0 <5.4", - "satooshi/php-coveralls": "^0.6.1", + "phpunit/phpunit": "^4.7|^5.0", "squizlabs/php_codesniffer": "^2.3" }, "suggest": { @@ -2497,7 +2647,7 @@ "identifier", "uuid" ], - "time": "2017-03-26T20:37:53+00:00" + "time": "2018-01-20T00:28:24+00:00" }, { "name": "s1lentium/iptools", @@ -2550,26 +2700,187 @@ "time": "2016-08-21T15:57:09+00:00" }, { - "name": "spatie/fractalistic", - "version": "2.2.0", + "name": "sofa/eloquence-base", + "version": "v5.5", "source": { "type": "git", - "url": "https://github.com/spatie/fractalistic.git", - "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639" + "url": "https://github.com/jarektkaczyk/eloquence-base.git", + "reference": "41e9b10073d0592b37437cdd06eea40a2b86f3e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/8f00c666a8b8dfb06f79286f97255e6ab1c89639", - "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639", + "url": "https://api.github.com/repos/jarektkaczyk/eloquence-base/zipball/41e9b10073d0592b37437cdd06eea40a2b86f3e0", + "reference": "41e9b10073d0592b37437cdd06eea40a2b86f3e0", "shasum": "" }, "require": { - "league/fractal": "^0.16.0", - "php": "^5.6|^7.0" + "illuminate/database": "5.5.*", + "php": ">=7.0.0", + "sofa/hookable": "5.5.*" + }, + "require-dev": { + "mockery/mockery": "0.9.4", + "phpunit/phpunit": "4.5.0", + "squizlabs/php_codesniffer": "2.3.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Sofa\\Eloquence\\BaseServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Sofa\\Eloquence\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jarek Tkaczyk", + "email": "jarek@softonsofa.com", + "homepage": "https://softonsofa.com/", + "role": "Developer" + } + ], + "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", + "keywords": [ + "eloquent", + "laravel", + "mappable", + "metable", + "mutable", + "searchable" + ], + "time": "2017-10-13T14:26:50+00:00" + }, + { + "name": "sofa/eloquence-validable", + "version": "v5.5", + "source": { + "type": "git", + "url": "https://github.com/jarektkaczyk/eloquence-validable.git", + "reference": "ac93ec8180558d3c70328de166c33a765732bb12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jarektkaczyk/eloquence-validable/zipball/ac93ec8180558d3c70328de166c33a765732bb12", + "reference": "ac93ec8180558d3c70328de166c33a765732bb12", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "sofa/eloquence-base": "5.5.*" + }, + "require-dev": { + "mockery/mockery": "0.9.4", + "phpunit/phpunit": "4.5.0", + "squizlabs/php_codesniffer": "2.3.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sofa\\Eloquence\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jarek Tkaczyk", + "email": "jarek@softonsofa.com", + "homepage": "https://softonsofa.com/", + "role": "Developer" + } + ], + "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", + "keywords": [ + "eloquent", + "laravel", + "mappable", + "metable", + "mutable", + "searchable" + ], + "time": "2017-10-13T14:42:08+00:00" + }, + { + "name": "sofa/hookable", + "version": "v5.5.1", + "source": { + "type": "git", + "url": "https://github.com/jarektkaczyk/hookable.git", + "reference": "7eb58b5cadeebca4ef74d5bd742dd4ff93348524" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jarektkaczyk/hookable/zipball/7eb58b5cadeebca4ef74d5bd742dd4ff93348524", + "reference": "7eb58b5cadeebca4ef74d5bd742dd4ff93348524", + "shasum": "" + }, + "require": { + "illuminate/database": "5.3.*|5.4.*|5.5.*", + "php": ">=5.6.4" + }, + "require-dev": { + "crysalead/kahlan": "~1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sofa\\Hookable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jarek Tkaczyk", + "email": "jarek@softonsofa.com", + "homepage": "http://softonsofa.com/", + "role": "Developer" + } + ], + "description": "Laravel Eloquent hooks system.", + "keywords": [ + "eloquent", + "laravel" + ], + "time": "2017-11-17T14:11:31+00:00" + }, + { + "name": "spatie/fractalistic", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/fractalistic.git", + "reference": "83e208ce1e6061aa75d22c4efdab05ccfb99a849" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/83e208ce1e6061aa75d22c4efdab05ccfb99a849", + "reference": "83e208ce1e6061aa75d22c4efdab05ccfb99a849", + "shasum": "" + }, + "require": { + "league/fractal": "^0.17.0", + "php": "^7.0" }, "require-dev": { "illuminate/pagination": "~5.3.0|~5.4.0", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^5.7.21" }, "type": "library", "autoload": { @@ -2598,33 +2909,43 @@ "spatie", "transform" ], - "time": "2017-05-29T14:16:20+00:00" + "time": "2018-01-15T09:17:52+00:00" }, { "name": "spatie/laravel-fractal", - "version": "4.0.1", + "version": "5.3.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053" + "reference": "e39e0865b66ca541337b45fed4ad6c22200b85d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/3b95780f5f3ca79e29d445a5df87eac9f7c7c053", - "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/e39e0865b66ca541337b45fed4ad6c22200b85d0", + "reference": "e39e0865b66ca541337b45fed4ad6c22200b85d0", "shasum": "" }, "require": { - "illuminate/contracts": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", - "illuminate/support": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", - "php": "^5.6|^7.0", - "spatie/fractalistic": "^2.0" + "illuminate/contracts": "~5.5.0|~5.6.0", + "illuminate/support": "~5.5.0|~5.6.0", + "php": "^7.0", + "spatie/fractalistic": "^2.5" }, "require-dev": { - "orchestra/testbench": "~3.2.0|3.3.0|~3.4.0", - "phpunit/phpunit": "^5.7.5" + "orchestra/testbench": "~3.5.0|~3.6.0", + "phpunit/phpunit": "^6.2|^7.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Fractal\\FractalServiceProvider" + ], + "aliases": { + "Fractal": "Spatie\\Fractal\\FractalFacade" + } + } + }, "autoload": { "psr-4": { "Spatie\\Fractal\\": "src" @@ -2656,33 +2977,34 @@ "spatie", "transform" ], - "time": "2017-05-05T19:01:43+00:00" + "time": "2018-02-07T23:06:44+00:00" }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.8", + "version": "v6.0.2", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517" + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517", - "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/412333372fb6c8ffb65496a2bbd7321af75733fc", + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc", "shasum": "" }, "require": { - "php": ">=5.3.3" + "egulias/email-validator": "~2.0", + "php": ">=7.0.0" }, "require-dev": { "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.2" + "symfony/phpunit-bridge": "~3.3@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -2704,55 +3026,55 @@ } ], "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "http://swiftmailer.org", + "homepage": "http://swiftmailer.symfony.com", "keywords": [ "email", "mail", "mailer" ], - "time": "2017-05-01T15:54:03+00:00" + "time": "2017-09-30T22:39:41+00:00" }, { "name": "symfony/console", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" + "reference": "26b6f419edda16c19775211987651cb27baea7f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", - "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", + "url": "https://api.github.com/repos/symfony/console/zipball/26b6f419edda16c19775211987651cb27baea7f1", + "reference": "26b6f419edda16c19775211987651cb27baea7f1", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/debug": "~2.8|~3.0", + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.3", - "symfony/dependency-injection": "~3.3", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/http-kernel": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", - "symfony/filesystem": "", + "symfony/lock": "", "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2779,29 +3101,29 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-06-02T19:24:58+00:00" + "time": "2018-01-29T09:03:43+00:00" }, { "name": "symfony/css-selector", - "version": "v3.3.2", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4d882dced7b995d5274293039370148e291808f2" + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2", - "reference": "4d882dced7b995d5274293039370148e291808f2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f97600434e3141ef3cbb9ea42cf500fba88022b7", + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2832,36 +3154,36 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-05-01T15:01:29+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/debug", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + "reference": "53f6af2805daf52a43b393b93d2f24925d35c937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", - "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "url": "https://api.github.com/repos/symfony/debug/zipball/53f6af2805daf52a43b393b93d2f24925d35c937", + "reference": "53f6af2805daf52a43b393b93d2f24925d35c937", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "psr/log": "~1.0" }, "conflict": { "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" }, "require-dev": { - "symfony/http-kernel": "~2.8|~3.0" + "symfony/http-kernel": "~2.8|~3.0|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2888,34 +3210,34 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2018-01-18T22:16:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.3.2", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4054a102470665451108f9b59305c79176ef98f0" + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", - "reference": "4054a102470665451108f9b59305c79176ef98f0", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74d33aac36208c4d6757807d9f598f0133a3a4eb", + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2924,7 +3246,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2951,29 +3273,29 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-04T18:15:29+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/finder", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + "reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "url": "https://api.github.com/repos/symfony/finder/zipball/613e26310776f49a1773b6737c6bd554b8bc8c6f", + "reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3000,33 +3322,34 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2018-01-03T07:37:34+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + "reference": "8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", - "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30", + "reference": "8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.1" + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0" + "symfony/expression-language": "~2.8|~3.0|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3053,56 +3376,58 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-06-05T13:06:51+00:00" + "time": "2018-01-29T09:03:43+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + "reference": "911d2e5dd4beb63caad9a72e43857de984301907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", - "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/911d2e5dd4beb63caad9a72e43857de984301907", + "reference": "911d2e5dd4beb63caad9a72e43857de984301907", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "psr/log": "~1.0", - "symfony/debug": "~2.8|~3.0", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/http-foundation": "~3.3" + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "^3.4.4|^4.0.4" }, "conflict": { "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.3", + "symfony/dependency-injection": "<3.4", "symfony/var-dumper": "<3.3", "twig/twig": "<1.34|<2.4,>=2" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0", + "symfony/browser-kit": "~2.8|~3.0|~4.0", "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0", - "symfony/console": "~2.8|~3.0", - "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/dom-crawler": "~2.8|~3.0", - "symfony/expression-language": "~2.8|~3.0", - "symfony/finder": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0", - "symfony/routing": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0", - "symfony/templating": "~2.8|~3.0", - "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.3" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/console": "~2.8|~3.0|~4.0", + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~3.3|~4.0" }, "suggest": { "symfony/browser-kit": "", - "symfony/class-loader": "", "symfony/config": "", "symfony/console": "", "symfony/dependency-injection": "", @@ -3112,7 +3437,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3139,20 +3464,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-06-06T03:59:58+00:00" + "time": "2018-01-29T12:29:46+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.4.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", "shasum": "" }, "require": { @@ -3164,7 +3489,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -3198,20 +3523,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.4.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929" + "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ebc999ce5f14204c5150b9bd15f8f04e621409d8", + "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8", "shasum": "" }, "require": { @@ -3221,7 +3546,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -3254,20 +3579,79 @@ "portable", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2018-01-30T19:27:44+00:00" }, { - "name": "symfony/polyfill-util", - "version": "v1.4.0", + "name": "symfony/polyfill-php70", + "version": "v1.7.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d" + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "e17c808ec4228026d4f5a8832afa19be85979563" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/e17c808ec4228026d4f5a8832afa19be85979563", + "reference": "e17c808ec4228026d4f5a8832afa19be85979563", "shasum": "" }, "require": { @@ -3276,7 +3660,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -3306,29 +3690,29 @@ "polyfill", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2018-01-31T18:08:44+00:00" }, { "name": "symfony/process", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" + "reference": "09a5172057be8fc677840e591b17f385e58c7c0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", - "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "url": "https://api.github.com/repos/symfony/process/zipball/09a5172057be8fc677840e591b17f385e58c7c0d", + "reference": "09a5172057be8fc677840e591b17f385e58c7c0d", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3355,39 +3739,39 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-05-22T12:32:03+00:00" + "time": "2018-01-29T09:03:43+00:00" }, { "name": "symfony/routing", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + "reference": "235d01730d553a97732990588407eaf6779bb4b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", - "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "url": "https://api.github.com/repos/symfony/routing/zipball/235d01730d553a97732990588407eaf6779bb4b2", + "reference": "235d01730d553a97732990588407eaf6779bb4b2", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/config": "<2.8", "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.3" + "symfony/yaml": "<3.4" }, "require-dev": { "doctrine/annotations": "~1.0", "doctrine/common": "~2.2", "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/http-foundation": "~2.8|~3.0", - "symfony/yaml": "~3.3" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -3400,7 +3784,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3433,35 +3817,38 @@ "uri", "url" ], - "time": "2017-06-02T09:51:43+00:00" + "time": "2018-01-16T18:03:57+00:00" }, { "name": "symfony/translation", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + "reference": "10b32cf0eae28b9b39fe26c456c42b19854c4b84" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", - "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "url": "https://api.github.com/repos/symfony/translation/zipball/10b32cf0eae28b9b39fe26c456c42b19854c4b84", + "reference": "10b32cf0eae28b9b39fe26c456c42b19854c4b84", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "symfony/config": "<2.8", - "symfony/yaml": "<3.3" + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/intl": "^2.8.18|^3.2.5", - "symfony/yaml": "~3.3" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "^2.8.18|^3.2.5|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "psr/log": "To use logging capability in translator", @@ -3471,7 +3858,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3498,24 +3885,24 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-05-22T07:42:36+00:00" + "time": "2018-01-18T22:16:57+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + "reference": "472a9849930cf21f73abdb02240f17cf5b5bd1a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", - "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/472a9849930cf21f73abdb02240f17cf5b5bd1a7", + "reference": "472a9849930cf21f73abdb02240f17cf5b5bd1a7", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -3527,12 +3914,13 @@ }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", "ext-symfony_debug": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -3566,33 +3954,33 @@ "debug", "dump" ], - "time": "2017-06-02T09:10:29+00:00" + "time": "2018-01-29T09:03:43+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b" + "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", - "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", + "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", "shasum": "" }, "require": { - "php": "^5.5 || ^7", - "symfony/css-selector": "^2.7|~3.0" + "php": "^5.5 || ^7.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0" }, "require-dev": { - "phpunit/phpunit": "~4.8|5.1.*" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -3613,7 +4001,7 @@ ], "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", - "time": "2016-09-20T12:50:39+00:00" + "time": "2017-11-27T11:13:29+00:00" }, { "name": "vlucas/phpdotenv", @@ -3666,33 +4054,35 @@ "time": "2016-09-01T10:05:43+00:00" }, { - "name": "webpatser/laravel-uuid", - "version": "2.0.1", + "name": "webmozart/assert", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/webpatser/laravel-uuid.git", - "reference": "6ed2705775e3edf066b90e1292f76f157ec00507" + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webpatser/laravel-uuid/zipball/6ed2705775e3edf066b90e1292f76f157ec00507", - "reference": "6ed2705775e3edf066b90e1292f76f157ec00507", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "fzaninotto/faker": "1.5.*", - "phpunit/phpunit": "4.7.*" - }, - "suggest": { - "paragonie/random_compat": "A random_bytes Php 5.x polyfill." + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, "autoload": { - "psr-0": { - "Webpatser\\Uuid": "src/" + "psr-4": { + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3701,43 +4091,166 @@ ], "authors": [ { - "name": "Christoph Kempen", - "email": "christoph@downsized.nl" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Class to generate a UUID according to the RFC 4122 standard. Support for version 1, 3, 4 and 5 UUID are built-in.", - "homepage": "https://github.com/webpatser/uuid", + "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ - "UUID RFC4122" + "assert", + "check", + "validate" ], - "time": "2016-05-09T09:22:18+00:00" + "time": "2018-01-29T19:49:41+00:00" + }, + { + "name": "znck/belongs-to-through", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/znck/belongs-to-through.git", + "reference": "8ac53e9134072902a8d3f3e18c327d4a8fd70d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/znck/belongs-to-through/zipball/8ac53e9134072902a8d3f3e18c327d4a8fd70d3d", + "reference": "8ac53e9134072902a8d3f3e18c327d4a8fd70d3d", + "shasum": "" + }, + "require": { + "illuminate/database": "~5.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "^1.11", + "orchestra/testbench": "~3.0", + "phpunit/php-code-coverage": "^3.3", + "phpunit/phpunit": "~5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Znck\\Eloquent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rahul Kadyan", + "email": "hi@znck.me" + } + ], + "description": "Adds belongsToThrough relation to laravel models", + "homepage": "https://github.com/znck/belongs-to-through", + "keywords": [ + "belongsToThrough", + "eloquent", + "laravel", + "model", + "models", + "znck" + ], + "time": "2017-07-23T13:11:16+00:00" } ], "packages-dev": [ { - "name": "barryvdh/laravel-ide-helper", - "version": "v2.3.2", + "name": "barryvdh/laravel-debugbar", + "version": "v3.1.1", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "f0018d359a2ad6968ad11b283283a925e017f3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f0018d359a2ad6968ad11b283283a925e017f3c9", + "reference": "f0018d359a2ad6968ad11b283283a925e017f3c9", + "shasum": "" + }, + "require": { + "illuminate/routing": "5.5.x|5.6.x", + "illuminate/session": "5.5.x|5.6.x", + "illuminate/support": "5.5.x|5.6.x", + "maximebf/debugbar": "~1.15.0", + "php": ">=7.0", + "symfony/debug": "^3|^4", + "symfony/finder": "^3|^4" + }, + "require-dev": { + "illuminate/framework": "5.5.x" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "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" + ], + "time": "2018-02-07T08:29:09+00:00" + }, + { + "name": "barryvdh/laravel-ide-helper", + "version": "v2.4.3", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "5c304db44fba8e9c4aa0c09739e59f7be7736fdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5c304db44fba8e9c4aa0c09739e59f7be7736fdd", + "reference": "5c304db44fba8e9c4aa0c09739e59f7be7736fdd", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.4", - "illuminate/console": "^5.0,<5.5", - "illuminate/filesystem": "^5.0,<5.5", - "illuminate/support": "^5.0,<5.5", + "illuminate/console": "^5.0,<5.7", + "illuminate/filesystem": "^5.0,<5.7", + "illuminate/support": "^5.0,<5.7", "php": ">=5.4.0", "symfony/class-loader": "^2.3|^3.0" }, "require-dev": { "doctrine/dbal": "~2.3", + "illuminate/config": "^5.0,<5.7", + "illuminate/view": "^5.0,<5.7", "phpunit/phpunit": "4.*", "scrutinizer/ocular": "~1.1", "squizlabs/php_codesniffer": "~2.3" @@ -3749,6 +4262,11 @@ "extra": { "branch-alias": { "dev-master": "2.3-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] } }, "autoload": { @@ -3778,7 +4296,7 @@ "phpstorm", "sublime" ], - "time": "2017-02-22T12:27:33+00:00" + "time": "2018-02-08T07:56:07+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -3829,6 +4347,55 @@ ], "time": "2016-06-13T19:28:20+00:00" }, + { + "name": "codedungeon/phpunit-result-printer", + "version": "0.6.0", + "source": { + "type": "git", + "url": "https://github.com/mikeerickson/phpunit-pretty-result-printer.git", + "reference": "34659fcb48ef35fc27c09ded46ac79b25ee95d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/34659fcb48ef35fc27c09ded46ac79b25ee95d2f", + "reference": "34659fcb48ef35fc27c09ded46ac79b25ee95d2f", + "shasum": "" + }, + "require": { + "hassankhan/config": "^0.10.0", + "php": "^7.1", + "symfony/yaml": "^2.7|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.2", + "spatie/phpunit-watcher": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codedungeon\\PHPUnitPrettyResultPrinter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike Erickson", + "email": "codedungeon@gmail.com" + } + ], + "description": "PHPUnit Pretty Result Printer", + "keywords": [ + "composer", + "package", + "phpunit", + "printer", + "result-printer" + ], + "time": "2018-02-04T21:48:07+00:00" + }, { "name": "composer/semver", "version": "1.4.2", @@ -3893,32 +4460,32 @@ }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { "athletic/athletic": "~0.1.8", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -3943,39 +4510,118 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2017-07-22T11:58:36+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v1.13.1", + "name": "filp/whoops", + "version": "2.1.14", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088" + "url": "https://github.com/filp/whoops.git", + "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", - "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", + "url": "https://api.github.com/repos/filp/whoops/zipball/c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", + "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^5.3.6 || >=7.0 <7.2", - "sebastian/diff": "^1.1", - "symfony/console": "^2.3 || ^3.0", - "symfony/event-dispatcher": "^2.1 || ^3.0", - "symfony/filesystem": "^2.1 || ^3.0", - "symfony/finder": "^2.1 || ^3.0", - "symfony/process": "^2.3 || ^3.0", - "symfony/stopwatch": "^2.5 || ^3.0" - }, - "conflict": { - "hhvm": "<3.9" + "php": "^5.5.9 || ^7.0", + "psr/log": "^1.0.1" }, "require-dev": { - "phpunit/phpunit": "^4.5|^5", - "satooshi/php-coveralls": "^1.0" + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "symfony/var-dumper": "^2.6 || ^3.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "time": "2017-11-23T18:22:44+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.10.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "74e4682a4073bc8bc2d4ff2b30a4873ac76cc1f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/74e4682a4073bc8bc2d4ff2b30a4873ac76cc1f1", + "reference": "74e4682a4073bc8bc2d4ff2b30a4873ac76cc1f1", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "gecko-packages/gecko-php-unit": "^2.0 || ^3.0", + "php": "^5.6 || >=7.0 <7.3", + "php-cs-fixer/diff": "^1.2", + "symfony/console": "^3.2 || ^4.0", + "symfony/event-dispatcher": "^3.0 || ^4.0", + "symfony/filesystem": "^3.0 || ^4.0", + "symfony/finder": "^3.0 || ^4.0", + "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0", + "symfony/stopwatch": "^3.0 || ^4.0" + }, + "conflict": { + "hhvm": "*" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.0", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.0", + "php-cs-fixer/accessible-object": "^1.0", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "phpunitgoodpractices/traits": "^1.0", + "symfony/phpunit-bridge": "^3.2.2 || ^4.0" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." }, "bin": [ "php-cs-fixer" @@ -3983,8 +4629,19 @@ "type": "application", "autoload": { "psr-4": { - "Symfony\\CS\\": "Symfony/CS/" - } + "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/TestCase.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4001,33 +4658,35 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2016-12-01T00:05:05+00:00" + "time": "2018-02-03T08:30:06+00:00" }, { "name": "fzaninotto/faker", - "version": "v1.6.0", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "ext-intl": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.8-dev" + } }, "autoload": { "psr-4": { @@ -4049,24 +4708,76 @@ "faker", "fixtures" ], - "time": "2016-04-29T12:21:54+00:00" + "time": "2017-08-15T16:48:10+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v1.2.2", + "name": "gecko-packages/gecko-php-unit", + "version": "v3.1.1", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", + "reference": "8b0320158e34c3d85e5133c341d55c4d6ec5e927" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/8b0320158e34c3d85e5133c341d55c4d6ec5e927", + "reference": "8b0320158e34c3d85e5133c341d55c4d6ec5e927", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.0" + }, + "conflict": { + "phpunit/phpunit": "<6.0 || >6.5" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-dom": "When testing with xml.", + "ext-libxml": "When testing with xml.", + "phpunit/phpunit": "This is an extension for PHPUnit so make sure you have that in some way." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "GeckoPackages\\PHPUnit\\": "src/PHPUnit" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Additional PHPUnit asserts and constraints.", + "homepage": "https://github.com/GeckoPackages", + "keywords": [ + "extension", + "filesystem", + "phpunit" + ], + "time": "2018-02-05T09:18:39+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -4075,15 +4786,18 @@ }, "require-dev": { "phpunit/php-file-iterator": "1.3.3", - "satooshi/php-coveralls": "dev-master" + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^1.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, "autoload": { "classmap": [ "hamcrest" - ], - "files": [ - "hamcrest/Hamcrest.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4094,34 +4808,152 @@ "keywords": [ "test" ], - "time": "2015-05-11T14:41:42+00:00" + "time": "2016-01-20T08:20:44+00:00" }, { - "name": "mockery/mockery", - "version": "0.9.9", + "name": "hassankhan/config", + "version": "0.10.0", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "6fdb61243844dc924071d3404bb23994ea0b6856" + "url": "https://github.com/hassankhan/config.git", + "reference": "06ac500348af033f1a2e44dc357ca86282626d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856", - "reference": "6fdb61243844dc924071d3404bb23994ea0b6856", + "url": "https://api.github.com/repos/hassankhan/config/zipball/06ac500348af033f1a2e44dc357ca86282626d4a", + "reference": "06ac500348af033f1a2e44dc357ca86282626d4a", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "~1.1", - "lib-pcre": ">=7.0", - "php": ">=5.3.2" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.0", + "scrutinizer/ocular": "~1.1", + "squizlabs/php_codesniffer": "~2.2" + }, + "suggest": { + "symfony/yaml": "~2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Noodlehaus\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hassan Khan", + "homepage": "http://hassankhan.me/", + "role": "Developer" + } + ], + "description": "Lightweight configuration file loader that supports PHP, INI, XML, JSON, and YAML files", + "homepage": "http://hassankhan.me/config/", + "keywords": [ + "config", + "configuration", + "ini", + "json", + "microphp", + "unframework", + "xml", + "yaml", + "yml" + ], + "time": "2016-02-11T16:21:17+00:00" + }, + { + "name": "maximebf/debugbar", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e7d60937ee5f1320975ca9bc7bcdd44d500f07", + "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.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": "0.9.x-dev" + "dev-master": "1.14-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" + ], + "time": "2017-12-15T11:13:46+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38", + "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~2.0", + "lib-pcre": ">=7.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7|~6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" } }, "autoload": { @@ -4146,7 +4978,7 @@ } ], "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/padraic/mockery", + "homepage": "http://github.com/mockery/mockery", "keywords": [ "BDD", "TDD", @@ -4159,41 +4991,44 @@ "test double", "testing" ], - "time": "2017-02-28T12:52:32+00:00" + "time": "2017-10-06T16:20:43+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -4201,20 +5036,338 @@ "object", "object graph" ], - "time": "2017-04-12T18:52:22+00:00" + "time": "2017-10-19T19:58:43+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0", + "name": "phar-io/manifest", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "b95b8c02c58670b15612cfc60873f3f7f5290484" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/b95b8c02c58670b15612cfc60873f3f7f5290484", + "reference": "b95b8c02c58670b15612cfc60873f3f7f5290484", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "time": "2017-10-21T10:28:17+00:00" + }, + { + "name": "php-mock/php-mock", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock.git", + "reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/22d297231118e6fd5b9db087fbe1ef866c2b95d2", + "reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "phpunit/php-text-template": "^1" + }, + "replace": { + "malkusch/php-mock": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\": [ + "classes/", + "tests/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2017-02-17T20:52:52+00:00" + }, + { + "name": "php-mock/php-mock-integration", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-integration.git", + "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/5a0d7d7755f823bc2a230cfa45058b40f9013bc4", + "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "php-mock/php-mock": "^2", + "phpunit/php-text-template": "^1" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\integration\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Integration package for PHP-Mock", + "homepage": "https://github.com/php-mock/php-mock-integration", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2017-02-17T21:31:34+00:00" + }, + { + "name": "php-mock/php-mock-phpunit", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-phpunit.git", + "reference": "b42fc41ecb7538564067527f6c30b8854f149d32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/b42fc41ecb7538564067527f6c30b8854f149d32", + "reference": "b42fc41ecb7538564067527f6c30b8854f149d32", + "shasum": "" + }, + "require": { + "php": ">=7", + "php-mock/php-mock-integration": "^2", + "phpunit/phpunit": "^6 <6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\phpunit\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-phpunit", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "phpunit", + "stub", + "test", + "test double" + ], + "time": "2017-12-02T09:49:02+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", "shasum": "" }, "require": { @@ -4255,33 +5408,39 @@ "reflection", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", "shasum": "" }, "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": [ @@ -4300,24 +5459,24 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -4347,37 +5506,37 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.7.0", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "sebastian/comparator": "^1.1|^2.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -4410,44 +5569,44 @@ "spy", "stub" ], - "time": "2017-03-02T20:05:34+00:00" + "time": "2017-11-24T13:59:53+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.8", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -4462,7 +5621,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -4473,20 +5632,20 @@ "testing", "xunit" ], - "time": "2017-04-02T07:44:40+00:00" + "time": "2017-12-06T09:29:45+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.2", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", "shasum": "" }, "require": { @@ -4520,7 +5679,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -4614,29 +5773,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.11", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4659,20 +5818,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-02-27T10:12:30+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.20", + "version": "6.4.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b" + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932", "shasum": "" }, "require": { @@ -4681,33 +5840,35 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "^1.4.3", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0.3|~2.0", - "symfony/yaml": "~2.1|~3.0" + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2.2", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^4.0.3", + "sebastian/comparator": "^2.0.2", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -4715,7 +5876,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7.x-dev" + "dev-master": "6.4.x-dev" } }, "autoload": { @@ -4741,33 +5902,33 @@ "testing", "xunit" ], - "time": "2017-05-22T07:42:55+00:00" + "time": "2017-11-08T11:26:09+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.4.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.0" }, "conflict": { - "phpunit/phpunit": "<5.4.0" + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-soap": "*" @@ -4775,7 +5936,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -4800,7 +5961,7 @@ "mock", "xunit" ], - "time": "2016-12-08T20:27:08+00:00" + "time": "2017-08-03T14:08:16+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -4849,30 +6010,30 @@ }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -4903,38 +6064,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", - "version": "1.4.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4961,32 +6122,32 @@ "keywords": [ "diff" ], - "time": "2017-05-22T07:24:03+00:00" + "time": "2017-08-03T08:09:46+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -5011,34 +6172,34 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -5078,27 +6239,27 @@ "export", "exporter" ], - "time": "2016-11-19T08:54:04+00:00" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -5106,7 +6267,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5129,33 +6290,34 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", - "version": "2.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -5175,32 +6337,77 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-02-18T15:18:39+00:00" + "time": "2017-08-03T12:35:26+00:00" }, { - "name": "sebastian/recursion-context", - "version": "2.0.0", + "name": "sebastian/object-reflector", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -5228,7 +6435,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19T07:33:16+00:00" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -5315,135 +6522,25 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, - { - "name": "sllh/php-cs-fixer-styleci-bridge", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/Soullivaneuh/php-cs-fixer-styleci-bridge.git", - "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Soullivaneuh/php-cs-fixer-styleci-bridge/zipball/2406ac6ab7a243e1b4d4f5f73c12e5905c629877", - "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877", - "shasum": "" - }, - "require": { - "composer/semver": "^1.0", - "doctrine/inflector": "^1.0", - "php": "^5.3 || ^7.0", - "sllh/styleci-fixers": "^3.0 || ^4.0", - "symfony/config": "^2.3 || ^3.0", - "symfony/console": "^2.3 || ^3.0", - "symfony/yaml": "^2.3 || ^3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^1.6.1", - "matthiasnoback/symfony-config-test": "^1.2", - "symfony/phpunit-bridge": "^2.7.4 || ^3.0", - "twig/twig": "^1.22" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "SLLH\\StyleCIBridge\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sullivan SENECHAL", - "email": "soullivaneuh@gmail.com" - } - ], - "description": "Auto configure PHP-CS-Fixer from StyleCI config file", - "keywords": [ - "PSR-1", - "PSR-2", - "PSR-4", - "StyleCI", - "configuration", - "laravel", - "php-cs-fixer", - "psr", - "symfony" - ], - "time": "2016-06-22T13:26:46+00:00" - }, - { - "name": "sllh/styleci-fixers", - "version": "v4.10.0", - "source": { - "type": "git", - "url": "https://github.com/Soullivaneuh/styleci-fixers.git", - "reference": "f2547f5cd465325bc7b996d53657189019e7ac05" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Soullivaneuh/styleci-fixers/zipball/f2547f5cd465325bc7b996d53657189019e7ac05", - "reference": "f2547f5cd465325bc7b996d53657189019e7ac05", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0" - }, - "require-dev": { - "styleci/sdk": "^1.0", - "symfony/console": "^3.0", - "twig/twig": "^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "SLLH\\StyleCIFixers\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sullivan SENECHAL", - "email": "soullivaneuh@gmail.com" - } - ], - "description": "Auto configure PHP-CS-Fixer from StyleCI config file", - "keywords": [ - "StyleCI", - "configuration", - "php-cs-fixer" - ], - "time": "2017-05-10T08:16:59+00:00" - }, { "name": "symfony/class-loader", - "version": "v3.3.2", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f" + "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f", - "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/e63c12699822bb3b667e7216ba07fbcc3a3e203e", + "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "require-dev": { - "symfony/finder": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0|~4.0", "symfony/polyfill-apcu": "~1.1" }, "suggest": { @@ -5452,7 +6549,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -5479,89 +6576,29 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2017-06-02T09:51:43+00:00" - }, - { - "name": "symfony/config", - "version": "v3.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", - "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/filesystem": "~2.8|~3.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "symfony/dependency-injection": "~3.3", - "symfony/yaml": "~3.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "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 Config Component", - "homepage": "https://symfony.com", - "time": "2017-06-02T18:07:20+00:00" + "time": "2018-01-03T07:37:34+00:00" }, { "name": "symfony/filesystem", - "version": "v3.3.2", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "c709670bf64721202ddbe4162846f250735842c0" + "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", - "reference": "c709670bf64721202ddbe4162846f250735842c0", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/760e47a4ee64b4c48f4b30017011e09d4c0f05ed", + "reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5588,29 +6625,138 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-05-28T14:08:56+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { - "name": "symfony/stopwatch", - "version": "v3.3.2", + "name": "symfony/options-resolver", + "version": "v4.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "371532a2cfe932f7a3766dd4c45364566def1dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/371532a2cfe932f7a3766dd4c45364566def1dd0", + "reference": "371532a2cfe932f7a3766dd4c45364566def1dd0", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2018-01-18T22:19:33+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "8eca20c8a369e069d4f4c2ac9895144112867422" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8eca20c8a369e069d4f4c2ac9895144112867422", + "reference": "8eca20c8a369e069d4f4c2ac9895144112867422", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-31T17:43:24+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/d52321f0e2b596bd03b5d1dd6eebe71caa925704", + "reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" } }, "autoload": { @@ -5637,27 +6783,30 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:14:56+00:00" + "time": "2018-01-03T07:38:00+00:00" }, { "name": "symfony/yaml", - "version": "v3.3.2", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063" + "reference": "ffc60bda1d4a00ec0b32eeabf39dc017bf480028" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/9752a30000a8ca9f4b34b5227d15d0101b96b063", - "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ffc60bda1d4a00ec0b32eeabf39dc017bf480028", + "reference": "ffc60bda1d4a00ec0b32eeabf39dc017bf480028", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" + }, + "conflict": { + "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~2.8|~3.0" + "symfony/console": "~3.4|~4.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -5665,7 +6814,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5692,57 +6841,47 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-06-02T22:05:06+00:00" + "time": "2018-01-21T19:06:11+00:00" }, { - "name": "webmozart/assert", - "version": "1.2.0", + "name": "theseer/tokenizer", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2016-11-23T20:04:58+00:00" + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" } ], "aliases": [], @@ -5751,10 +6890,10 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.0.0", + "php": ">=7.2", "ext-mbstring": "*", - "ext-zip": "*", - "ext-pdo_mysql": "*" + "ext-pdo_mysql": "*", + "ext-zip": "*" }, "platform-dev": [] } diff --git a/config/app.php b/config/app.php index c049872c5..1376becd6 100644 --- a/config/app.php +++ b/config/app.php @@ -1,10 +1,15 @@ env('APP_ENV', 'production'), - - 'version' => env('APP_VERSION', '0.6.4'), + 'version' => '0.7.0', /* |-------------------------------------------------------------------------- @@ -15,7 +20,21 @@ return [ | framework needs to place the application's name in a notification or | any other location as required by the application or its packages. */ - 'name' => 'Pterodactyl', + + 'name' => env('APP_NAME', 'Pterodactyl'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services your application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- @@ -67,7 +86,7 @@ return [ | */ - 'locale' => 'en', + 'locale' => env('APP_LOCALE', 'en'), /* |-------------------------------------------------------------------------- @@ -93,7 +112,7 @@ return [ | */ - 'key' => env('APP_KEY', 'SomeRandomString3232RandomString'), + 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', @@ -112,7 +131,23 @@ return [ 'log' => env('APP_LOG', 'daily'), - 'log_level' => env('APP_LOG_LEVEL', 'debug'), + 'log_level' => env('APP_LOG_LEVEL', 'info'), + + /* + |-------------------------------------------------------------------------- + | Exception Reporter Configuration + |-------------------------------------------------------------------------- + | + | If you're encountering weird behavior with the Panel and no exceptions + | are being logged try changing the environment variable below to be true. + | This will override the default "don't report" behavior of the Panel and log + | all exceptions. This will be quite noisy. + | + */ + + 'exceptions' => [ + 'report_all' => env('APP_REPORT_ALL_EXCEPTIONS', false), + ], /* |-------------------------------------------------------------------------- @@ -126,7 +161,6 @@ return [ */ 'providers' => [ - /* * Laravel Framework Service Providers... */ @@ -142,6 +176,7 @@ return [ Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, @@ -150,36 +185,27 @@ return [ Illuminate\Session\SessionServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - - /* - * Package Service Providers... - */ - Laravel\Tinker\TinkerServiceProvider::class, /* * Application Service Providers... */ Pterodactyl\Providers\AppServiceProvider::class, Pterodactyl\Providers\AuthServiceProvider::class, + Pterodactyl\Providers\BladeServiceProvider::class, Pterodactyl\Providers\EventServiceProvider::class, + Pterodactyl\Providers\HashidsServiceProvider::class, Pterodactyl\Providers\RouteServiceProvider::class, Pterodactyl\Providers\MacroServiceProvider::class, Pterodactyl\Providers\PhraseAppTranslationProvider::class, + Pterodactyl\Providers\RepositoryServiceProvider::class, + Pterodactyl\Providers\ViewComposerServiceProvider::class, /* * Additional Dependencies */ - Barryvdh\Debugbar\ServiceProvider::class, - PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, igaster\laravelTheme\themeServiceProvider::class, Prologue\Alerts\AlertsServiceProvider::class, - Krucas\Settings\Providers\SettingsServiceProvider::class, - Fideloper\Proxy\TrustedProxyServiceProvider::class, - Laracasts\Utilities\JavaScript\JavaScriptServiceProvider::class, Lord\Laroute\LarouteServiceProvider::class, - Spatie\Fractal\FractalServiceProvider::class, - ], /* @@ -194,53 +220,42 @@ return [ */ 'aliases' => [ - - '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, - 'Cron' => Cron\CronExpression::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Debugbar' => Barryvdh\Debugbar\Facade::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Fractal' => Spatie\Fractal\FractalFacade::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, - 'Inspiring' => Illuminate\Foundation\Inspiring::class, + '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, + '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, - 'Settings' => Krucas\Settings\Facades\Settings::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'Theme' => igaster\laravelTheme\Facades\Theme::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Uuid' => Webpatser\Uuid\Uuid::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, + 'Theme' => igaster\laravelTheme\Facades\Theme::class, + 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, - 'Version' => Pterodactyl\Facades\Version::class, - 'View' => Illuminate\Support\Facades\View::class, - + 'View' => Illuminate\Support\Facades\View::class, ], - ]; diff --git a/config/auth.php b/config/auth.php index 6f7fc83c2..e83406286 100644 --- a/config/auth.php +++ b/config/auth.php @@ -1,6 +1,20 @@ [ + 'time' => 120, + 'attempts' => 3, + ], /* |-------------------------------------------------------------------------- @@ -97,5 +111,4 @@ return [ 'expire' => 60, ], ], - ]; diff --git a/config/broadcasting.php b/config/broadcasting.php index 85e045124..9c4c792de 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -1,7 +1,6 @@ [ - 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_KEY'), @@ -48,7 +46,5 @@ return [ 'null' => [ 'driver' => 'null', ], - ], - ]; diff --git a/config/cache.php b/config/cache.php index 7bd9dd70e..86bbeb61e 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,7 +1,6 @@ env('CACHE_DRIVER', 'file'), @@ -27,7 +28,6 @@ return [ */ 'stores' => [ - 'apc' => [ 'driver' => 'apc', ], @@ -41,6 +41,7 @@ return [ 'table' => 'cache', 'connection' => null, ], + 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), @@ -69,7 +70,6 @@ return [ 'driver' => 'redis', 'connection' => 'default', ], - ], /* @@ -83,6 +83,5 @@ return [ | */ - 'prefix' => 'laravel', - + 'prefix' => env('CACHE_PREFIX', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_cache'), ]; diff --git a/config/compile.php b/config/compile.php index 04807eac4..cbf7739a6 100644 --- a/config/compile.php +++ b/config/compile.php @@ -1,7 +1,6 @@ [ - // ], /* @@ -29,7 +27,5 @@ return [ */ 'providers' => [ - // ], - ]; diff --git a/config/database.php b/config/database.php index 58324a0b5..acbc8627b 100644 --- a/config/database.php +++ b/config/database.php @@ -1,7 +1,6 @@ [ 'mysql' => [ - 'driver' => 'mysql', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'strict' => false, + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'panel'), + 'username' => env('DB_USERNAME', 'pterodactyl'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => env('DB_PREFIX', ''), + 'strict' => env('DB_STRICT_MODE', false), ], ], @@ -76,8 +75,7 @@ return [ 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), - 'database' => 0, + 'database' => env('REDIS_DATBASE', 0), ], ], - ]; diff --git a/config/debugbar.php b/config/debugbar.php index 05e78c34c..f1a1fd753 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -1,7 +1,6 @@ [ - '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 + '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 + '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 ], /* @@ -118,14 +117,14 @@ return [ '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. + '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 + 'hints' => false, // Show hints for common mistakes ], 'mail' => [ 'full_log' => false, @@ -165,5 +164,4 @@ return [ | */ 'route_prefix' => '_debugbar', - ]; diff --git a/config/filesystems.php b/config/filesystems.php index e726fda0c..809742bed 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -1,7 +1,6 @@ 'local', + 'default' => env('FILESYSTEM_DRIVER', 'local'), /* |-------------------------------------------------------------------------- @@ -28,7 +25,7 @@ return [ | */ - 'cloud' => 's3', + 'cloud' => env('FILESYSTEM_CLOUD', 's3'), /* |-------------------------------------------------------------------------- @@ -43,7 +40,6 @@ return [ | */ 'disks' => [ - 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), @@ -58,12 +54,10 @@ return [ 's3' => [ 'driver' => 's3', - 'key' => env('AWS_KEY'), - 'secret' => env('AWS_SECRET'), - 'region' => env('AWS_REGION'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ], - ], - ]; diff --git a/config/laravel-fractal.php b/config/fractal.php similarity index 55% rename from config/laravel-fractal.php rename to config/fractal.php index 32ced203e..833cf2b95 100644 --- a/config/laravel-fractal.php +++ b/config/fractal.php @@ -1,7 +1,6 @@ League\Fractal\Serializer\JsonApiSerializer::class, + /* + |-------------------------------------------------------------------------- + | Auto Includes + |-------------------------------------------------------------------------- + | + | If enabled Fractal will automatically add the includes who's + | names are present in the `include` request parameter. + | + */ + + 'auto_includes' => [ + 'enabled' => true, + 'request_key' => 'include', + ], ]; diff --git a/config/hashids.php b/config/hashids.php new file mode 100644 index 000000000..199de1a32 --- /dev/null +++ b/config/hashids.php @@ -0,0 +1,15 @@ + env('HASHIDS_SALT'), + 'length' => env('HASHIDS_LENGTH', 8), + 'alphabet' => env('HASHIDS_ALPHABET', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'), +]; diff --git a/config/ide-helper.php b/config/ide-helper.php new file mode 100644 index 000000000..9f10873f6 --- /dev/null +++ b/config/ide-helper.php @@ -0,0 +1,175 @@ + '_ide_helper', + 'format' => 'php', + + /* + |-------------------------------------------------------------------------- + | Fluent helpers + |-------------------------------------------------------------------------- + | + | Set to true to generate commonly used Fluent methods + | + */ + + 'include_fluent' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model Magic methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write magic methods of model + | + */ + + 'write_model_magic_where' => true, + + /* + |-------------------------------------------------------------------------- + | Helper files to include + |-------------------------------------------------------------------------- + | + | Include helper files. By default not included, but can be toggled with the + | -- helpers (-H) option. Extra helper files can be included. + | + */ + + 'include_helpers' => false, + + 'helper_files' => [ + base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + ], + + /* + |-------------------------------------------------------------------------- + | Model locations to include + |-------------------------------------------------------------------------- + | + | Define in which directories the ide-helper:models command should look + | for models. + | + */ + + 'model_locations' => [ + 'app/Models', + ], + + /* + |-------------------------------------------------------------------------- + | Extra classes + |-------------------------------------------------------------------------- + | + | These implementations are not really extended, but called with magic functions + | + */ + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [ + 'Log' => [ + 'debug' => 'Monolog\Logger::addDebug', + 'info' => 'Monolog\Logger::addInfo', + 'notice' => 'Monolog\Logger::addNotice', + 'warning' => 'Monolog\Logger::addWarning', + 'error' => 'Monolog\Logger::addError', + 'critical' => 'Monolog\Logger::addCritical', + 'alert' => 'Monolog\Logger::addAlert', + 'emergency' => 'Monolog\Logger::addEmergency', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Interface implementations + |-------------------------------------------------------------------------- + | + | These interfaces will be replaced with the implementing class. Some interfaces + | are detected by the helpers, others can be listed below. + | + */ + + 'interfaces' => [ + ], + + /* + |-------------------------------------------------------------------------- + | Support for custom DB types + |-------------------------------------------------------------------------- + | + | This setting allow you to map any custom database type (that you may have + | created using CREATE TYPE statement or imported using database plugin + | / extension to a Doctrine type. + | + | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: + | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' + | + | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant + | + | The value of the array is an array of type mappings. Key is the name of the custom type, + | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in + | our case it is 'json_array'. Doctrine types are listed here: + | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html + | + | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: + | + | "postgresql" => array( + | "jsonb" => "json_array", + | ), + | + */ + 'custom_db_types' => [ + ], + + /* + |-------------------------------------------------------------------------- + | Support for camel cased models + |-------------------------------------------------------------------------- + | + | There are some Laravel packages (such as Eloquence) that allow for accessing + | Eloquent model properties via camel case, instead of snake case. + | + | Enabling this option will support these packages by saving all model + | properties as camel case, instead of snake case. + | + | For example, normally you would see this: + | + | * @property \Carbon\Carbon $created_at + | * @property \Carbon\Carbon $updated_at + | + | With this enabled, the properties will be this: + | + | * @property \Carbon\Carbon $createdAt + | * @property \Carbon\Carbon $updatedAt + | + | Note, it is currently an all-or-nothing option. + | + */ + 'model_camel_case_properties' => false, + + /* + |-------------------------------------------------------------------------- + | Property Casts + |-------------------------------------------------------------------------- + | + | Cast the given "real type" to the given "type". + | + */ + 'type_overrides' => [ + 'integer' => 'int', + 'boolean' => 'bool', + ], +]; diff --git a/config/javascript.php b/config/javascript.php index 1aeb222e0..57504395d 100644 --- a/config/javascript.php +++ b/config/javascript.php @@ -1,7 +1,6 @@ 'Pterodactyl', - ]; diff --git a/config/laroute.php b/config/laroute.php index 7b332c40a..b2b4a2f30 100644 --- a/config/laroute.php +++ b/config/laroute.php @@ -1,7 +1,6 @@ '', - ]; diff --git a/config/mail.php b/config/mail.php index 146e1b11c..f47793438 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,7 +1,6 @@ 'alert_messages', - ]; diff --git a/config/pterodactyl.php b/config/pterodactyl.php index bd10183c6..523080ae3 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -1,6 +1,17 @@ (bool) env('APP_ENVIRONMENT_ONLY', false), /* |-------------------------------------------------------------------------- @@ -12,8 +23,7 @@ return [ | standard Pterodactyl shipped services. */ 'service' => [ - 'core' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', - 'author' => env('SERVICE_AUTHOR'), + 'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'), ], /* @@ -24,7 +34,12 @@ return [ | Should login success and failure events trigger an email to the user? */ 'auth' => [ - 'notifications' => env('LOGIN_NOTIFICATIONS', false), + '2fa_required' => env('APP_2FA_REQUIRED', 0), + '2fa' => [ + 'bytes' => 32, + 'window' => env('APP_2FA_WINDOW', 4), + 'verify_newer' => true, + ], ], /* @@ -39,6 +54,11 @@ return [ 'frontend' => [ 'servers' => env('APP_PAGINATE_FRONT_SERVERS', 15), ], + 'admin' => [ + 'servers' => env('APP_PAGINATE_ADMIN_SERVERS', 25), + 'users' => env('APP_PAGINATE_ADMIN_USERS', 25), + 'packs' => env('APP_PAGINATE_ADMIN_PACKS', 50), + ], 'api' => [ 'nodes' => env('APP_PAGINATE_API_NODES', 25), 'servers' => env('APP_PAGINATE_API_SERVERS', 25), @@ -55,6 +75,7 @@ return [ */ 'api' => [ 'include_on_list' => env('API_INCLUDE_ON_LIST', false), + 'key_expire_time' => env('API_KEY_EXPIRE_TIME', 60 * 12), ], /* @@ -94,6 +115,17 @@ return [ 'frequency' => env('CONSOLE_PUSH_FREQ', 200), ], + /* + |-------------------------------------------------------------------------- + | Daemon Connection Details + |-------------------------------------------------------------------------- + | + | Configuration for support of the new Golang based daemon. + */ + 'daemon' => [ + 'use_new_daemon' => (bool) env('APP_USE_NEW_DAEMON', false), + ], + /* |-------------------------------------------------------------------------- | Task Timers @@ -115,7 +147,7 @@ return [ | if panel is up to date. */ 'cdn' => [ - 'cache' => 60, + 'cache_time' => 60, 'url' => 'https://cdn.pterodactyl.io/releases/latest.json', ], @@ -131,6 +163,30 @@ return [ 'in_context' => env('PHRASE_IN_CONTEXT', false), ], + /* + |-------------------------------------------------------------------------- + | File Editor + |-------------------------------------------------------------------------- + | + | This array includes the MIME filetypes that can be edited via the web. + */ + 'files' => [ + 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 50000), + 'editable' => [ + 'application/json', + 'application/javascript', + 'application/xml', + 'application/xhtml+xml', + 'inode/x-empty', + 'text/xml', + 'text/css', + 'text/html', + 'text/plain', + 'text/x-perl', + 'text/x-shellscript', + ], + ], + /* |-------------------------------------------------------------------------- | JSON Response Routes @@ -144,4 +200,21 @@ return [ 'daemon/*', 'remote/*', ], + + 'default_api_version' => 'application/vnd.pterodactyl.v1+json', + + /* + |-------------------------------------------------------------------------- + | Dynamic Environment Variables + |-------------------------------------------------------------------------- + | + | Place dynamic environment variables here that should be auto-appended + | to server environment fields when the server is created or updated. + | + | Items should be in 'key' => 'value' format, where key is the environment + | variable name, and value is the server-object key. For example: + | + | 'P_SERVER_CREATED_AT' => 'created_at' + */ + 'environment_variables' => [], ]; diff --git a/config/queue.php b/config/queue.php index a3d726f4a..ed004a52a 100644 --- a/config/queue.php +++ b/config/queue.php @@ -1,7 +1,6 @@ [ - 'sync' => [ 'driver' => 'sync', ], @@ -43,20 +41,19 @@ return [ 'sqs' => [ 'driver' => 'sqs', - 'key' => env('SQS_KEY'), + 'key' => env('SQS_KEY'), 'secret' => env('SQS_SECRET'), 'prefix' => env('SQS_QUEUE_PREFIX'), - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => env('QUEUE_STANDARD', 'standard'), 'region' => env('SQS_REGION', 'us-east-1'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => env('QUEUE_STANDARD', 'standard'), 'retry_after' => 90, ], - ], /* @@ -74,5 +71,4 @@ return [ 'database' => 'mysql', 'table' => 'failed_jobs', ], - ]; diff --git a/config/recaptcha.php b/config/recaptcha.php index 646c9931a..22d739481 100644 --- a/config/recaptcha.php +++ b/config/recaptcha.php @@ -1,7 +1,6 @@ env('RECAPTCHA_SECRET_KEY', '6LekAxoUAAAAAPW-PxNWaCLH76WkClMLSa2jImwD'), + 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5'), + '_shipped_secret_key' => '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5', /* * Use a custom website key, we use our public one by default */ - 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LekAxoUAAAAADjWZJ4ufcDRZBBiH9vfHawqRbup'), + 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn'), + '_shipped_website_key' => '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn', /* * Domain verification is enabled by default and compares the domain used when solving the captcha * as public keys can't have domain verification on google's side enabled (obviously). */ 'verify_domain' => true, - ]; diff --git a/config/services.php b/config/services.php index 6ea80674c..be637e501 100644 --- a/config/services.php +++ b/config/services.php @@ -1,10 +1,9 @@ [ - 'key' => env('SES_KEY'), + 'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), 'region' => 'us-east-1', ], @@ -32,5 +31,4 @@ return [ 'sparkpost' => [ 'secret' => env('SPARKPOST_SECRET'), ], - ]; diff --git a/config/session.php b/config/session.php index 0c1b3bb8e..837809f66 100644 --- a/config/session.php +++ b/config/session.php @@ -1,7 +1,6 @@ 10080, + 'lifetime' => env('SESSION_LIFETIME', 10080), 'expire_on_close' => false, @@ -122,7 +121,7 @@ return [ | */ - 'cookie' => 'pterodactyl_session', + 'cookie' => env('SESSION_COOKIE', str_slug(env('APP_NAME', 'pterodactyl'), '_') . '_session'), /* |-------------------------------------------------------------------------- @@ -176,4 +175,18 @@ return [ 'http_only' => true, + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | 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. + | + | Supported: "lax", "strict" + | + */ + + 'same_site' => null, ]; diff --git a/config/settings.php b/config/settings.php deleted file mode 100644 index e5c82eea0..000000000 --- a/config/settings.php +++ /dev/null @@ -1,118 +0,0 @@ - env('SETTINGS_DRIVER', 'database'), - - /* - |-------------------------------------------------------------------------- - | Enable / Disable caching - |-------------------------------------------------------------------------- - | - | If it is enabled all values gets cached after accessing it. - | - */ - 'cache' => true, - - /* - |-------------------------------------------------------------------------- - | Enable / Disable value encryption - |-------------------------------------------------------------------------- - | - | If it is enabled all values gets encrypted and decrypted. - | - */ - 'encryption' => env('SETTINGS_ENCRYPTION', false), - - /* - |-------------------------------------------------------------------------- - | Enable / Disable events - |-------------------------------------------------------------------------- - | - | If it is enabled various settings related events will be fired. - | - */ - 'events' => true, - - /* - |-------------------------------------------------------------------------- - | Repositories Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure the driver information for each repository that - | is used by your application. A default configuration has been added - | for each back-end shipped with this package. You are free to add more. - | - */ - - 'repositories' => [ - - 'database' => [ - 'driver' => 'database', - 'connection' => env('DB_CONNECTION', 'mysql'), - 'table' => 'settings', - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Key generator class - |-------------------------------------------------------------------------- - | - | Key generator is used to generate keys based on setting key and context. - | - */ - 'key_generator' => \Krucas\Settings\KeyGenerators\KeyGenerator::class, - - /* - |-------------------------------------------------------------------------- - | Context serializer class - |-------------------------------------------------------------------------- - | - | Context serializer serializes context. - | It is used with "Krucas\Settings\KeyGenerators\KeyGenerator" class. - | - */ - 'context_serializer' => \Krucas\Settings\ContextSerializers\ContextSerializer::class, - - /* - |-------------------------------------------------------------------------- - | Value serializer class - |-------------------------------------------------------------------------- - | - | Value serializer serializes / unserializes given value. - | - */ - 'value_serializer' => \Krucas\Settings\ValueSerializers\ValueSerializer::class, - - /* - |-------------------------------------------------------------------------- - | Override application config values - |-------------------------------------------------------------------------- - | - | If defined, settings package will override these config values from persistent - | settings repository. - | - | Sample: - | "app.fallback_locale", - | "app.locale" => "settings.locale", - | - */ - - 'override' => [ - - ], - -]; diff --git a/config/themes.php b/config/themes.php index f90a29f68..1a7083681 100644 --- a/config/themes.php +++ b/config/themes.php @@ -8,9 +8,9 @@ return [ 'themes' => [ 'pterodactyl' => [ - 'extends' => null, - 'views-path' => 'pterodactyl', - 'asset-path' => 'themes/pterodactyl', + 'extends' => null, + 'views-path' => 'pterodactyl', + 'asset-path' => 'themes/pterodactyl', ], ], ]; diff --git a/config/trustedproxy.php b/config/trustedproxy.php index a06211497..c60aa6a06 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -1,7 +1,6 @@ [ - \Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', - \Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + \Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + \Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', \Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', - \Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + \Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', ], ]; diff --git a/config/view.php b/config/view.php index 2acfd9cc9..8796b0abc 100644 --- a/config/view.php +++ b/config/view.php @@ -1,7 +1,6 @@ realpath(storage_path('framework/views')), - ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 7295947ce..bef8ee396 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -1,5 +1,7 @@ define(Pterodactyl\Models\User::class, function (Faker\Generator $faker) { +$factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'name' => $faker->name, - 'email' => $faker->email, - 'password' => bcrypt(str_random(10)), - 'remember_token' => str_random(10), + 'id' => $faker->unique()->randomNumber(), + 'node_id' => $faker->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'uuidShort' => str_random(8), + 'name' => $faker->firstName, + 'description' => implode(' ', $faker->sentences()), + 'skip_scripts' => 0, + 'suspended' => 0, + 'memory' => 512, + 'swap' => 0, + 'disk' => 512, + 'io' => 500, + 'cpu' => 0, + 'oom_disabled' => 0, + 'allocation_id' => $faker->randomNumber(), + 'nest_id' => $faker->randomNumber(), + 'egg_id' => $faker->randomNumber(), + 'pack_id' => null, + 'installed' => 1, + 'created_at' => \Carbon\Carbon::now(), + 'updated_at' => \Carbon\Carbon::now(), + ]; +}); + +$factory->define(Pterodactyl\Models\User::class, function (Faker $faker) { + static $password; + + return [ + 'id' => $faker->unique()->randomNumber(), + 'external_id' => null, + 'uuid' => $faker->uuid, + 'username' => $faker->userName, + 'email' => $faker->safeEmail, + 'name_first' => $faker->firstName, + 'name_last' => $faker->lastName, + 'password' => $password ?: $password = bcrypt('password'), + 'language' => 'en', + 'root_admin' => false, + 'use_totp' => false, + ]; +}); + +$factory->state(Pterodactyl\Models\User::class, 'admin', function () { + return [ + 'root_admin' => true, + ]; +}); + +$factory->define(Pterodactyl\Models\Location::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'short' => $faker->domainWord, + 'long' => $faker->catchPhrase, + ]; +}); + +$factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'public' => true, + 'name' => $faker->firstName, + 'fqdn' => $faker->ipv4, + 'scheme' => 'http', + 'behind_proxy' => false, + 'memory' => 1024, + 'memory_overallocate' => 0, + 'disk' => 10240, + 'disk_overallocate' => 0, + 'upload_size' => 100, + 'daemonSecret' => $faker->uuid, + 'daemonListen' => 8080, + 'daemonSFTP' => 2022, + 'daemonBase' => '/srv/daemon', + ]; +}); + +$factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'author' => 'testauthor@example.com', + 'name' => $faker->word, + 'description' => null, + ]; +}); + +$factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'nest_id' => $faker->unique()->randomNumber(), + 'name' => $faker->name, + 'description' => implode(' ', $faker->sentences(3)), + 'startup' => 'java -jar test.jar', + ]; +}); + +$factory->define(Pterodactyl\Models\EggVariable::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'name' => $faker->firstName, + 'description' => $faker->sentence(), + 'env_variable' => strtoupper(str_replace(' ', '_', $faker->words(2, true))), + 'default_value' => $faker->colorName, + 'user_viewable' => 0, + 'user_editable' => 0, + 'rules' => 'required|string', + ]; +}); + +$factory->state(Pterodactyl\Models\EggVariable::class, 'viewable', function () { + return ['user_viewable' => 1]; +}); + +$factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () { + return ['user_editable' => 1]; +}); + +$factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'egg_id' => $faker->randomNumber(), + 'uuid' => $faker->uuid, + 'name' => $faker->word, + 'description' => null, + 'version' => $faker->randomNumber(), + 'selectable' => 1, + 'visible' => 1, + 'locked' => 0, + ]; +}); + +$factory->define(Pterodactyl\Models\Subuser::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'user_id' => $faker->randomNumber(), + 'server_id' => $faker->randomNumber(), + ]; +}); + +$factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'node_id' => $faker->randomNumber(), + 'ip' => $faker->ipv4, + 'port' => $faker->randomNumber(5), + ]; +}); + +$factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'name' => $faker->colorName, + 'host' => $faker->unique()->ipv4, + 'port' => 3306, + 'username' => $faker->colorName, + 'password' => Crypt::encrypt($faker->word), + 'node_id' => $faker->randomNumber(), + ]; +}); + +$factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { + static $password; + + return [ + 'id' => $faker->unique()->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'database_host_id' => $faker->randomNumber(), + 'database' => str_random(10), + 'username' => str_random(10), + 'remote' => '%', + 'password' => $password ?: bcrypt('test123'), + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + ]; +}); + +$factory->define(Pterodactyl\Models\Schedule::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'name' => $faker->firstName(), + ]; +}); + +$factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'schedule_id' => $faker->randomNumber(), + 'sequence_id' => $faker->randomNumber(1), + 'action' => 'command', + 'payload' => 'test command', + 'time_offset' => 120, + 'is_queued' => false, + ]; +}); + +$factory->define(Pterodactyl\Models\DaemonKey::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'user_id' => $faker->randomNumber(), + 'secret' => 'i_' . str_random(40), + 'expires_at' => \Carbon\Carbon::now()->addMinutes(10)->toDateTimeString(), + ]; +}); + +$factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'user_id' => $faker->randomNumber(), + 'identifier' => str_random(Pterodactyl\Models\ApiKey::IDENTIFIER_LENGTH), + 'token' => 'encrypted_string', + 'memo' => 'Test Function Key', + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + ]; +}); + +$factory->define(Pterodactyl\Models\APIPermission::class, function (Faker $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'key_id' => $faker->randomNumber(), + 'permission' => mb_strtolower($faker->word), ]; }); 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 7384b7de2..cfff2b359 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -7,8 +7,6 @@ class AddAllocationsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AddAllocationsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 290c124a0..af7deb62d 100644 --- a/database/migrations/2016_01_23_195851_add_api_keys.php +++ b/database/migrations/2016_01_23_195851_add_api_keys.php @@ -7,8 +7,6 @@ class AddApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 f1843aad5..e6f6bcbf8 100644 --- a/database/migrations/2016_01_23_200044_add_api_permissions.php +++ b/database/migrations/2016_01_23_200044_add_api_permissions.php @@ -7,8 +7,6 @@ class AddApiPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddApiPermissions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200159_add_downloads.php b/database/migrations/2016_01_23_200159_add_downloads.php index f1c192432..b1771c5e4 100644 --- a/database/migrations/2016_01_23_200159_add_downloads.php +++ b/database/migrations/2016_01_23_200159_add_downloads.php @@ -7,8 +7,6 @@ class AddDownloads extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddDownloads extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 63eaf53a6..83923e7d0 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 @@ -7,8 +7,6 @@ class CreateFailedJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class CreateFailedJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 01d3a9e65..277acae31 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -7,8 +7,6 @@ class CreateJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -27,8 +25,6 @@ class CreateJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200528_add_locations.php b/database/migrations/2016_01_23_200528_add_locations.php index 6ad7de75b..b34a5fbcc 100644 --- a/database/migrations/2016_01_23_200528_add_locations.php +++ b/database/migrations/2016_01_23_200528_add_locations.php @@ -7,8 +7,6 @@ class AddLocations extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddLocations extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200648_add_nodes.php b/database/migrations/2016_01_23_200648_add_nodes.php index 57613cabd..52c0a29e6 100644 --- a/database/migrations/2016_01_23_200648_add_nodes.php +++ b/database/migrations/2016_01_23_200648_add_nodes.php @@ -7,8 +7,6 @@ class AddNodes extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddNodes extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 45454b801..0584e3617 100644 --- a/database/migrations/2016_01_23_201433_add_password_resets.php +++ b/database/migrations/2016_01_23_201433_add_password_resets.php @@ -7,8 +7,6 @@ class AddPasswordResets extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddPasswordResets extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201531_add_permissions.php b/database/migrations/2016_01_23_201531_add_permissions.php index bf6327ac6..12c9bbe0f 100644 --- a/database/migrations/2016_01_23_201531_add_permissions.php +++ b/database/migrations/2016_01_23_201531_add_permissions.php @@ -7,8 +7,6 @@ class AddPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddPermissions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 229b33c60..d9a436e6d 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -7,8 +7,6 @@ class AddServerVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddServerVariables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201748_add_servers.php b/database/migrations/2016_01_23_201748_add_servers.php index f4ae554c7..5e1061069 100644 --- a/database/migrations/2016_01_23_201748_add_servers.php +++ b/database/migrations/2016_01_23_201748_add_servers.php @@ -7,8 +7,6 @@ class AddServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -40,8 +38,6 @@ class AddServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 172f1eb7b..7b0a33609 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -7,8 +7,6 @@ class AddServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class AddServiceOptions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 5ea938986..e79fa1fe9 100644 --- a/database/migrations/2016_01_23_202731_add_service_varibles.php +++ b/database/migrations/2016_01_23_202731_add_service_varibles.php @@ -7,8 +7,6 @@ class AddServiceVaribles extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -29,8 +27,6 @@ class AddServiceVaribles extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_202943_add_services.php b/database/migrations/2016_01_23_202943_add_services.php index c837dc923..31f723445 100644 --- a/database/migrations/2016_01_23_202943_add_services.php +++ b/database/migrations/2016_01_23_202943_add_services.php @@ -7,8 +7,6 @@ class AddServices extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class AddServices extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 0e2c0cbfe..2cd6922c2 100644 --- a/database/migrations/2016_01_23_203119_create_settings_table.php +++ b/database/migrations/2016_01_23_203119_create_settings_table.php @@ -7,8 +7,6 @@ class CreateSettingsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class CreateSettingsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203150_add_subusers.php b/database/migrations/2016_01_23_203150_add_subusers.php index 1cc8e334c..2f0e46310 100644 --- a/database/migrations/2016_01_23_203150_add_subusers.php +++ b/database/migrations/2016_01_23_203150_add_subusers.php @@ -7,8 +7,6 @@ class AddSubusers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddSubusers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203159_add_users.php b/database/migrations/2016_01_23_203159_add_users.php index 14e28eeba..05ace7e22 100644 --- a/database/migrations/2016_01_23_203159_add_users.php +++ b/database/migrations/2016_01_23_203159_add_users.php @@ -7,8 +7,6 @@ class AddUsers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class AddUsers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 f1c84aa4b..533fa8aa2 100644 --- a/database/migrations/2016_01_23_203947_create_sessions_table.php +++ b/database/migrations/2016_01_23_203947_create_sessions_table.php @@ -7,8 +7,6 @@ class CreateSessionsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CreateSessionsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 c0632b1d9..ae46dceb2 100644 --- a/database/migrations/2016_01_25_234418_rename_permissions_column.php +++ b/database/migrations/2016_01_25_234418_rename_permissions_column.php @@ -7,8 +7,6 @@ class RenamePermissionsColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,13 +17,10 @@ class RenamePermissionsColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 9a36a690a..7b1048b15 100644 --- a/database/migrations/2016_02_07_172148_add_databases_tables.php +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -7,8 +7,6 @@ class AddDatabasesTables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -26,8 +24,6 @@ class AddDatabasesTables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 9d6da726e..5a6740ae6 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 @@ -7,8 +7,6 @@ class AddDatabaseServersTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -27,8 +25,6 @@ class AddDatabaseServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 1f8ad36fc..c8255ff47 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 @@ -7,8 +7,6 @@ class AddServiceOptionDefaultStartup extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServiceOptionDefaultStartup extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 798ac4ba9..01ff91359 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 @@ -7,8 +7,6 @@ class AddUniqueServiceField extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddUniqueServiceField extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 2cdaa30b6..f4cb7b1e3 100644 --- a/database/migrations/2016_02_27_163411_add_tasks_table.php +++ b/database/migrations/2016_02_27_163411_add_tasks_table.php @@ -7,8 +7,6 @@ class AddTasksTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,16 +23,14 @@ class AddTasksTable extends Migration $table->string('day_of_month')->default('*'); $table->string('hour')->default('*'); $table->string('minute')->default('*'); - $table->timestamp('last_run'); - $table->timestamp('next_run'); + $table->timestamp('last_run')->nullable(); + $table->timestamp('next_run')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. - * - * @return void */ public function down() { 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 6d9352dff..265e7fd96 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 @@ -7,8 +7,6 @@ class AddTasksLogTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AddTasksLogTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 index 9065947d6..9d4752eb6 100644 --- a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php +++ b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php @@ -6,8 +6,6 @@ class AddNullableFieldLastrun extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -17,8 +15,6 @@ class AddNullableFieldLastrun extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 e75930edd..26aa5eaa5 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -7,8 +7,6 @@ class AddIpAlias extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -30,8 +28,6 @@ class AddIpAlias extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 b77ccbea6..ee7e704fb 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 @@ -7,8 +7,6 @@ class ModifyIpStorageMethod extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -48,8 +46,6 @@ class ModifyIpStorageMethod extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 39717253b..7bfb75b20 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 @@ -7,8 +7,6 @@ class AddSuspensionForServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddSuspensionForServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 746559a80..22a2bde13 100644 --- a/database/migrations/2016_09_01_211924_remove_active_column.php +++ b/database/migrations/2016_09_01_211924_remove_active_column.php @@ -7,8 +7,6 @@ class RemoveActiveColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class RemoveActiveColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 b950c631b..565957d59 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 @@ -7,8 +7,6 @@ class AddSftpPasswordStorage extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddSftpPasswordStorage extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 482bb08c7..840ecacb5 100644 --- a/database/migrations/2016_09_04_171338_update_jobs_tables.php +++ b/database/migrations/2016_09_04_171338_update_jobs_tables.php @@ -8,8 +8,6 @@ class UpdateJobsTables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class UpdateJobsTables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 38f2ef3a5..a00f5f18d 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 @@ -8,8 +8,6 @@ class UpdateFailedJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class UpdateFailedJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 160a1c42f..30fc23a59 100644 --- a/database/migrations/2016_09_04_182835_create_notifications_table.php +++ b/database/migrations/2016_09_04_182835_create_notifications_table.php @@ -7,8 +7,6 @@ class CreateNotificationsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CreateNotificationsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 d26e5995a..e1bab9ccc 100644 --- a/database/migrations/2016_09_07_163017_add_unique_identifier.php +++ b/database/migrations/2016_09_07_163017_add_unique_identifier.php @@ -8,8 +8,6 @@ class AddUniqueIdentifier extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddUniqueIdentifier extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 d8a4e8c65..a7df1ca1b 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 @@ -8,8 +8,6 @@ class AllowLongerRegexField extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AllowLongerRegexField extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 58e4b87a3..05d26112e 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 @@ -8,8 +8,6 @@ class AddDockerImageColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddDockerImageColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 064c57c3b..14ae07c4a 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 @@ -8,8 +8,6 @@ class UpdateServersColumnName extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class UpdateServersColumnName extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 4fecb8bdf..adb577754 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -6,8 +6,6 @@ class RenameDoubleInsurgency extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,11 +20,8 @@ class RenameDoubleInsurgency extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { - // } } 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 5f1ceb22f..08ea312dc 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 @@ -8,8 +8,6 @@ class BuildApiLogTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -29,8 +27,6 @@ class BuildApiLogTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 80d7f9501..56c3e8097 100644 --- a/database/migrations/2016_10_14_164802_update_api_keys.php +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -8,8 +8,6 @@ class UpdateApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class UpdateApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 0a5316755..70ec18b33 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -6,8 +6,6 @@ class UpdateMisnamedBungee extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class UpdateMisnamedBungee extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 59bc0ac39..3a2663527 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 @@ -8,8 +8,6 @@ class AddForeignKeysServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddForeignKeysServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 8ff9bdd2f..0660081cb 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -8,8 +8,6 @@ class AddForeignAllocations extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,24 +22,22 @@ class AddForeignAllocations extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropForeign('allocations_node_foreign'); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign('allocations_assigned_to_foreign'); + $table->dropForeign('allocations_node_foreign'); - $table->dropIndex('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); - }); + $table->dropIndex('allocations_assigned_to_foreign'); + $table->dropIndex('allocations_node_foreign'); + }); - DB::statement('ALTER TABLE allocations + DB::statement('ALTER TABLE allocations MODIFY COLUMN assigned_to MEDIUMINT(8) UNSIGNED NULL, MODIFY COLUMN node MEDIUMINT(8) UNSIGNED NOT NULL '); - } + } } 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 67dcd3ce3..700342d74 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 @@ -8,8 +8,6 @@ class AddForeignApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 58bf1a5a5..d8eb3504d 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 @@ -8,8 +8,6 @@ class AddForeignApiPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignApiPermissions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('api_permissions', function (Blueprint $table) { - $table->dropForeign('api_permissions_key_id_foreign'); - $table->dropIndex('api_permissions_key_id_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('api_permissions', function (Blueprint $table) { + $table->dropForeign('api_permissions_key_id_foreign'); + $table->dropIndex('api_permissions_key_id_foreign'); + }); - DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); + } } 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 b6f012dff..769b7daa3 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 @@ -8,8 +8,6 @@ class AddForeignDatabaseServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignDatabaseServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 2d6b6e9a2..be26e3cb0 100644 --- a/database/migrations/2016_10_23_203105_add_foreign_databases.php +++ b/database/migrations/2016_10_23_203105_add_foreign_databases.php @@ -8,8 +8,6 @@ class AddForeignDatabases extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddForeignDatabases extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 8fa516c00..f861e0a7d 100644 --- a/database/migrations/2016_10_23_203335_add_foreign_nodes.php +++ b/database/migrations/2016_10_23_203335_add_foreign_nodes.php @@ -8,8 +8,6 @@ class AddForeignNodes extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddForeignNodes extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 153ab27ce..a43f0eacf 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -8,8 +8,6 @@ class AddForeignPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,19 +17,17 @@ class AddForeignPermissions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_user_id_foreign'); - $table->dropForeign('permissions_server_id_foreign'); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_user_id_foreign'); + $table->dropForeign('permissions_server_id_foreign'); - $table->dropIndex('permissions_user_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); - }); - } + $table->dropIndex('permissions_user_id_foreign'); + $table->dropIndex('permissions_server_id_foreign'); + }); + } } 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 af78a161c..b4720495d 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 @@ -8,8 +8,6 @@ class AddForeignServerVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,21 +22,19 @@ class AddForeignServerVariables extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('server_variables', function (Blueprint $table) { - $table->dropForeign(['server_id']); - $table->dropForeign(['variable_id']); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + }); - DB::statement('ALTER TABLE server_variables + 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 0baad0c36..cb8c0e2e8 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 @@ -8,8 +8,6 @@ class AddForeignServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignServiceOptions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign('service_options_parent_service_foreign'); + $table->dropIndex('service_options_parent_service_foreign'); + }); - DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); + } } 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 291ca24e2..02bbc46f2 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 @@ -8,8 +8,6 @@ class AddForeignServiceVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignServiceVariables extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('service_variables', function (Blueprint $table) { - $table->dropForeign('service_variables_option_id_foreign'); - $table->dropIndex('service_variables_option_id_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign('service_variables_option_id_foreign'); + $table->dropIndex('service_variables_option_id_foreign'); + }); - DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); + } } 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 2f4045669..b637c80ae 100644 --- a/database/migrations/2016_10_23_204454_add_foreign_subusers.php +++ b/database/migrations/2016_10_23_204454_add_foreign_subusers.php @@ -8,8 +8,6 @@ class AddForeignSubusers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddForeignSubusers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 3821caffc..18ea297e5 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -8,8 +8,6 @@ class AddForeignTasks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignTasks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 5a2dd6da4..4383c11cd 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 @@ -6,8 +6,6 @@ class AddArkServiceOptionFixed extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -74,8 +72,6 @@ class AddArkServiceOptionFixed extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 7cb3eb10e..b6fa0972b 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -8,8 +8,6 @@ class AddPackSupport extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -30,8 +28,6 @@ class AddPackSupport extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 4db76f8e8..42b0f6953 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 @@ -8,8 +8,6 @@ class SetServiceNameUnique extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class SetServiceNameUnique extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 4e3507c39..d520466a8 100644 --- a/database/migrations/2016_11_27_142519_add_pack_column.php +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -8,8 +8,6 @@ class AddPackColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddPackColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 91ef1fbbd..d2d14f4d0 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 @@ -8,8 +8,6 @@ class AddConfigurableUploadLimit extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddConfigurableUploadLimit extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 dd99c1223..e9c87989a 100644 --- a/database/migrations/2016_12_02_185206_correct_service_variables.php +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -6,8 +6,6 @@ class CorrectServiceVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -67,8 +65,6 @@ class CorrectServiceVariables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 a03584ca0..7cdf96807 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 @@ -6,8 +6,6 @@ class FixMisnamedOptionTag extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class FixMisnamedOptionTag extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 905d28a46..77693c265 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 @@ -8,8 +8,6 @@ class CreateNodeConfigurationTokensTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 67bc3f59d..0206040b5 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 @@ -9,8 +9,6 @@ class AddMoreUserData extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -35,8 +33,6 @@ class AddMoreUserData extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php index 233780bfd..c88aa8de7 100644 --- a/database/migrations/2017_02_02_175548_UpdateColumnNames.php +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -8,8 +8,6 @@ class UpdateColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -48,8 +46,6 @@ class UpdateColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php index 4408e612b..58ec63ef4 100644 --- a/database/migrations/2017_02_03_140948_UpdateNodesTable.php +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -8,8 +8,6 @@ class UpdateNodesTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class UpdateNodesTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php index 519ccc826..5f617abec 100644 --- a/database/migrations/2017_02_03_155554_RenameColumns.php +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -8,8 +8,6 @@ class RenameColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class RenameColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php index ddb37b891..c7688f056 100644 --- a/database/migrations/2017_02_05_164123_AdjustColumnNames.php +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -8,8 +8,6 @@ class AdjustColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AdjustColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php index 5e57ffef3..6f86b3b6e 100644 --- a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -8,8 +8,6 @@ class AdjustColumnNamesForServicePacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php index 7194bf075..45efce83a 100644 --- a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -10,8 +10,6 @@ class SetupPermissionsPivotTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -43,8 +41,6 @@ class SetupPermissionsPivotTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php index 358f9938d..8b541d941 100644 --- a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -8,8 +8,6 @@ class UpdateAPIKeyColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class UpdateAPIKeyColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php index 5931518d6..4f27346fa 100644 --- a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -8,8 +8,6 @@ class UpdateNodeConfigTokensColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index e4af2511a..2918e1afd 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -1,6 +1,5 @@ get() as $option) { - $option->servers->each(function ($s) use ($option) { - $prepend = $option->display_executable; - $prepend = ($prepend === './ShooterGameServer') ? './ShooterGame/Binaries/Linux/ShooterGameServer' : $prepend; - $prepend = ($prepend === 'TerrariaServer.exe') ? 'mono TerrariaServer.exe' : $prepend; - - $s->startup = $prepend . ' ' . $s->startup; - - $container = $s->container; - if (starts_with($container, 'quay.io/pterodactyl/minecraft')) { - $s->container = 'quay.io/pterodactyl/core:java'; - } elseif (starts_with($container, 'quay.io/pterodactyl/srcds')) { - $s->container = 'quay.io/pterodactyl/core:source'; - } elseif (starts_with($container, 'quay.io/pterodactyl/voice')) { - $s->container = 'quay.io/pterodactyl/core:glibc'; - } elseif (starts_with($container, 'quay.io/pterodactyl/terraria')) { - $s->container = 'quay.io/pterodactyl/core:mono'; - } - - $s->save(); - }); - } - Schema::table('services', function (Blueprint $table) { $table->renameColumn('file', 'folder'); $table->dropColumn('executable'); @@ -51,8 +23,6 @@ class DeleteServiceExecutableOption extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php index 351327d3c..385004fa4 100644 --- a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -8,8 +8,6 @@ class AddNewServiceOptionsColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class AddNewServiceOptionsColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index ee247ee96..039976352 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -3,44 +3,25 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; use Illuminate\Database\Migrations\Migration; class MigrateToNewServiceSystem extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { DB::transaction(function () { - $service = Service::where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); + $service = DB::table('services')->where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); if (! $service) { return; } - $options = ServiceOption::where('service_id', $service->id)->get(); + $options = DB::table('service_options')->where('service_id', $service->id)->get(); $options->each(function ($item) use ($options) { if ($item->tag === 'srcds' && $item->name === 'Insurgency') { $item->tag = 'insurgency'; @@ -56,8 +37,6 @@ class MigrateToNewServiceSystem extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 617c349fa..21fa51465 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -1,7 +1,6 @@ get() as $variable) { $variable->rules = ($variable->required) ? 'required|regex:' . $variable->rules : 'regex:' . $variable->rules; $variable->save(); } @@ -32,8 +29,6 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { @@ -43,7 +38,7 @@ class ChangeServiceVariablesValidationRules extends Migration }); DB::transaction(function () { - foreach (ServiceVariable::all() as $variable) { + foreach (DB::table('service_variables')->get() as $variable) { $variable->regex = str_replace(['required|regex:', 'regex:'], '', $variable->regex); $variable->save(); } diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index bbd5fda42..3628ba7a4 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -1,6 +1,5 @@ where('folder', '!=', 'minecraft')->update([ + DB::table('services')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', '!=', 'minecraft')->update([ 'index_file' => $this->default, ]); - Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([ + DB::table('services')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([ 'index_file' => $this->default_mc, ]); }); @@ -107,8 +104,6 @@ EOF; /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php index 910fb79df..d01012e41 100644 --- a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -8,8 +8,6 @@ class RenameServicePacksToSingluarPacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -26,8 +24,6 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php index 4916cd028..b1a8ee3a0 100644 --- a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -8,8 +8,6 @@ class AddLockedStatusToTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddLockedStatusToTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php index 50699a688..a7166df9e 100644 --- a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -8,8 +8,6 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php index 07192f898..bc6fb45c7 100644 --- a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -8,8 +8,6 @@ class CleanupDatabasesDatabase extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CleanupDatabasesDatabase extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php index 0271616a0..3f26a1e34 100644 --- a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -8,8 +8,6 @@ class AddForeignKeyToPacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignKeyToPacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php index 1e5ce0273..e8ebcb20d 100644 --- a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -8,8 +8,6 @@ class AddServerDescriptionColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServerDescriptionColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php index ccd318654..3cd08f1a9 100644 --- a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -8,8 +8,6 @@ class DropDeletedAtColumnFromServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class DropDeletedAtColumnFromServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php index 5264122b4..dc58df4d9 100644 --- a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -9,8 +9,6 @@ class UpgradeTaskSystem extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -34,8 +32,6 @@ class UpgradeTaskSystem extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php index 610f18e5f..ba2f57c41 100644 --- a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -8,8 +8,6 @@ class AddScriptsToServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddScriptsToServiceOptions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php index 07afdfeea..2bc8f27b3 100644 --- a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -8,8 +8,6 @@ class AddServiceScriptTrackingToServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServiceScriptTrackingToServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php index 027d1964b..514d17e1c 100644 --- a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -8,8 +8,6 @@ class AddCopyScriptFromColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddCopyScriptFromColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php index f82d39258..aa5e04498 100644 --- a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -8,8 +8,6 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php index 90a7f7a6a..7dcae3c6f 100644 --- a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -8,8 +8,6 @@ class DeleteDownloadTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class DeleteDownloadTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php index 369c867be..90c8c4b1e 100644 --- a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -8,8 +8,6 @@ class DeleteNodeConfigurationTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class DeleteNodeConfigurationTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { 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 new file mode 100644 index 000000000..9ce5057e8 --- /dev/null +++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php @@ -0,0 +1,28 @@ +unsignedInteger('external_id')->after('id')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + 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 new file mode 100644 index 000000000..a089ab4db --- /dev/null +++ b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php @@ -0,0 +1,32 @@ +dropForeign(['key_id']); + + $table->foreign('key_id')->references('id')->on('api_keys')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('api_permissions', function (Blueprint $table) { + $table->dropForeign(['key_id']); + + $table->foreign('key_id')->references('id')->on('api_keys'); + }); + } +} diff --git a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php new file mode 100644 index 000000000..0bfc7d527 --- /dev/null +++ b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php @@ -0,0 +1,48 @@ +dropForeign(['subuser_id']); + + $table->foreign('subuser_id')->references('id')->on('subusers')->onDelete('cascade'); + }); + + Schema::table('subusers', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['server_id']); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('subusers', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['server_id']); + + $table->foreign('user_id')->references('id')->on('users'); + $table->foreign('server_id')->references('id')->on('servers'); + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign(['subuser_id']); + + $table->foreign('subuser_id')->references('id')->on('subusers'); + }); + } +} diff --git a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php new file mode 100644 index 000000000..fb156ba8c --- /dev/null +++ b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php @@ -0,0 +1,32 @@ +dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php new file mode 100644 index 000000000..5ae9a29f9 --- /dev/null +++ b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php @@ -0,0 +1,36 @@ +dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + $table->foreign('variable_id')->references('id')->on('service_variables')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + $table->foreign('variable_id')->references('id')->on('service_variables'); + }); + } +} diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php new file mode 100644 index 000000000..88e2e0135 --- /dev/null +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php new file mode 100644 index 000000000..a33b78af6 --- /dev/null +++ b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php @@ -0,0 +1,30 @@ +dropForeign(['node_id']); + $table->foreign('node_id')->references('id')->on('nodes')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('database_hosts', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php new file mode 100644 index 000000000..77b7f984c --- /dev/null +++ b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php @@ -0,0 +1,30 @@ +integer('disk_overallocate')->default(0)->nullable(false)->change(); + $table->integer('memory_overallocate')->default(0)->nullable(false)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + 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 new file mode 100644 index 000000000..f7aab7c04 --- /dev/null +++ b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php @@ -0,0 +1,30 @@ +unique(['node_id', 'ip', 'port']); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->dropUnique(['node_id', 'ip', 'port']); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php new file mode 100644 index 000000000..074f872e0 --- /dev/null +++ b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['service_id']); + + $table->foreign('service_id')->references('id')->on('services')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['service_id']); + + $table->foreign('service_id')->references('id')->on('services'); + }); + } +} diff --git a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php new file mode 100644 index 000000000..1b8f1a567 --- /dev/null +++ b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php new file mode 100644 index 000000000..12eada73c --- /dev/null +++ b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php @@ -0,0 +1,23 @@ +increments('id'); + $table->unsignedInteger('server_id'); + $table->string('name')->nullable(); + $table->string('cron_day_of_week'); + $table->string('cron_day_of_month'); + $table->string('cron_hour'); + $table->string('cron_minute'); + $table->boolean('is_active'); + $table->boolean('is_processing'); + $table->timestamp('last_run_at')->nullable(); + $table->timestamp('next_run_at')->nullable(); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('schedules'); + } +} diff --git a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php new file mode 100644 index 000000000..9c225a834 --- /dev/null +++ b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php @@ -0,0 +1,36 @@ +increments('id'); + $table->unsignedInteger('schedule_id'); + $table->unsignedInteger('sequence_id'); + $table->string('action'); + $table->text('payload'); + $table->unsignedInteger('time_offset'); + $table->boolean('is_queued'); + $table->timestamps(); + + $table->index(['schedule_id', 'sequence_id']); + $table->foreign('schedule_id')->references('id')->on('schedules')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('tasks'); + } +} diff --git a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php new file mode 100644 index 000000000..2a20ef10e --- /dev/null +++ b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php @@ -0,0 +1,75 @@ +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(), + ]); + + 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(); + }); + + Schema::dropIfExists('tasks_old'); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::create('tasks_old', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('user_id')->nullable(); + $table->unsignedInteger('server_id'); + $table->tinyInteger('active')->default(1); + $table->string('action'); + $table->text('data'); + $table->unsignedTinyInteger('queued')->default(0); + $table->string('year')->default('*'); + $table->string('month')->default('*'); + $table->string('day_of_week')->default('*'); + $table->string('day_of_month')->default('*'); + $table->string('minute')->default('*'); + $table->timestamp('last_run')->nullable(); + $table->timestamp('next_run'); + $table->timestamps(); + }); + } +} diff --git a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php new file mode 100644 index 000000000..b7c361f13 --- /dev/null +++ b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php @@ -0,0 +1,43 @@ +where('permission', 'like', '%-task%')->get(); + foreach ($permissions as $record) { + $parts = explode('-', $record->permission); + if (! in_array(array_get($parts, 1), ['tasks', 'task']) || count($parts) !== 2) { + continue; + } + + $newPermission = $parts[0] . '-' . str_replace('task', 'schedule', $parts[1]); + + DB::table('permissions')->where('id', '=', $record->id)->update(['permission' => $newPermission]); + } + } + + /** + * Reverse the migrations. + */ + public function down() + { + $permissions = DB::table('permissions')->where('permission', 'like', '%-schedule%')->get(); + foreach ($permissions as $record) { + $parts = explode('-', $record->permission); + if (! in_array(array_get($parts, 1), ['schedules', 'schedule']) || count($parts) !== 2) { + continue; + } + + $newPermission = $parts[0] . '-' . str_replace('schedule', 'task', $parts[1]); + + DB::table('permissions')->where('id', '=', $record->id)->update(['permission' => $newPermission]); + } + } +} diff --git a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php new file mode 100644 index 000000000..cfbfc88b0 --- /dev/null +++ b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php @@ -0,0 +1,35 @@ +increments('id'); + $table->unsignedInteger('server_id'); + $table->unsignedInteger('user_id'); + $table->string('secret')->unique(); + $table->timestamp('expires_at'); + $table->timestamps(); + + $table->index(['server_id', 'user_id']); + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('daemon_keys'); + } +} diff --git a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php new file mode 100644 index 000000000..84cb2d92b --- /dev/null +++ b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php @@ -0,0 +1,52 @@ +select('id', 'owner_id')->get(); + $servers->each(function ($server) use (&$inserts) { + $inserts[] = [ + 'user_id' => $server->owner_id, + 'server_id' => $server->id, + 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), + 'expires_at' => Carbon::now()->addMinutes(config('pterodactyl.api.key_expire_time', 720))->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), + ]; + }); + + DB::transaction(function () use ($inserts) { + DB::table('daemon_keys')->insert($inserts); + }); + + Schema::table('servers', function (Blueprint $table) { + $table->dropUnique(['daemonSecret']); + $table->dropColumn('daemonSecret'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->char('daemonSecret', 36)->after('startup')->unique(); + }); + + DB::table('daemon_keys')->truncate(); + } +} diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php new file mode 100644 index 000000000..d4d2dd695 --- /dev/null +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -0,0 +1,57 @@ +get(); + $subusers->each(function ($subuser) use (&$inserts) { + $inserts[] = [ + 'user_id' => $subuser->user_id, + 'server_id' => $subuser->server_id, + 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), + 'expires_at' => Carbon::now()->addMinutes(config('pterodactyl.api.key_expire_time', 720))->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), + ]; + }); + + DB::transaction(function () use ($inserts) { + DB::table('daemon_keys')->insert($inserts); + }); + + Schema::table('subusers', function (Blueprint $table) { + $table->dropUnique(['daemonSecret']); + $table->dropColumn('daemonSecret'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('subusers', function (Blueprint $table) { + $table->char('daemonSecret', 36)->after('server_id'); + }); + + $subusers = DB::table('subusers')->get(); + $subusers->each(function ($subuser) { + DB::table('daemon_keys')->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); + }); + + Schema::table('subusers', function (Blueprint $table) { + $table->unique('daemonSecret'); + }); + } +} diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php new file mode 100644 index 000000000..6bb36813d --- /dev/null +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -0,0 +1,55 @@ +dropUnique(['name']); + $table->dropUnique(['file']); + + $table->string('author')->change(); + $table->char('uuid', 36)->after('id'); + $table->dropColumn('folder'); + $table->dropColumn('startup'); + $table->dropColumn('index_file'); + }); + + DB::table('services')->get(['id', 'author', 'uuid'])->each(function ($service) { + DB::table('services')->where('id', $service->id)->update([ + 'author' => ($service->author === 'ptrdctyl-v040-11e6-8b77-86f30ca893d3') ? 'support@pterodactyl.io' : 'unknown@unknown-author.com', + 'uuid' => Uuid::uuid4()->toString(), + ]); + }); + + Schema::table('services', function (Blueprint $table) { + $table->unique('uuid'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('uuid'); + $table->string('folder')->nullable(); + $table->text('startup')->nullable(); + $table->text('index_file'); + $table->string('author', 36)->change(); + + $table->unique('name'); + $table->unique('folder', 'services_file_unique'); + }); + } +} diff --git a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php new file mode 100644 index 000000000..5c9df79a5 --- /dev/null +++ b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php @@ -0,0 +1,59 @@ +char('uuid', 36)->after('id'); + $table->string('author')->after('service_id'); + $table->dropColumn('tag'); + }); + + DB::transaction(function () { + DB::table('service_options')->select([ + 'service_options.id', + 'service_options.uuid', + 'services.author AS service_author', + ])->join('services', 'services.id', '=', 'service_options.service_id')->get()->each(function ($option) { + DB::table('service_options')->where('id', $option->id)->update([ + 'author' => $option->service_author, + 'uuid' => Uuid::uuid4()->toString(), + ]); + }); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->unique('uuid'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropColumn('uuid'); + $table->dropColumn('author'); + $table->string('tag'); + }); + + DB::transaction(function () { + DB::table('service_options')->select(['id', 'tag'])->get()->each(function ($option) { + DB::table('service_options')->where('id', $option->id)->update([ + 'tag' => str_random(10), + ]); + }); + }); + } +} diff --git a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php new file mode 100644 index 000000000..3b19e3d99 --- /dev/null +++ b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('CASCADE'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php new file mode 100644 index 000000000..e7b70136c --- /dev/null +++ b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php @@ -0,0 +1,59 @@ +dropForeign(['service_id']); + $table->renameColumn('service_id', 'nest_id'); + + $table->foreign('nest_id')->references('id')->on('nests'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['service_id']); + $table->renameColumn('service_id', 'nest_id'); + + $table->foreign('nest_id')->references('id')->on('nests')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::rename('nests', 'services'); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['nest_id']); + $table->renameColumn('nest_id', 'service_id'); + + $table->foreign('service_id')->references('id')->on('services'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['nest_id']); + $table->renameColumn('nest_id', 'service_id'); + + $table->foreign('service_id')->references('id')->on('services')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php b/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php new file mode 100644 index 000000000..d2c55c057 --- /dev/null +++ b/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php @@ -0,0 +1,93 @@ +dropForeign(['config_from']); + $table->dropForeign(['copy_script_from']); + }); + + Schema::rename('service_options', 'eggs'); + + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs')->onDelete('CASCADE'); + }); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs'); + }); + + Schema::table('eggs', function (Blueprint $table) { + $table->foreign('config_from')->references('id')->on('eggs')->onDelete('SET NULL'); + $table->foreign('copy_script_from')->references('id')->on('eggs')->onDelete('SET NULL'); + }); + + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::table('eggs', function (Blueprint $table) { + $table->dropForeign(['config_from']); + $table->dropForeign(['copy_script_from']); + }); + + Schema::rename('eggs', 'service_options'); + + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('CASCADE'); + }); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->foreign('config_from')->references('id')->on('service_options')->onDelete('SET NULL'); + $table->foreign('copy_script_from')->references('id')->on('service_options')->onDelete('SET NULL'); + }); + + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('options')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php b/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php new file mode 100644 index 000000000..ef7d3811d --- /dev/null +++ b/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php @@ -0,0 +1,43 @@ +dropForeign(['variable_id']); + + $table->foreign('variable_id')->references('id')->on('egg_variables')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::rename('egg_variables', 'service_variables'); + + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['variable_id']); + + $table->foreign('variable_id')->references('id')->on('service_variables')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php new file mode 100644 index 000000000..e41acd275 --- /dev/null +++ b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php @@ -0,0 +1,32 @@ +dropUnique(['username']); + + $table->dropColumn('username'); + $table->dropColumn('sftp_password'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->string('username')->nullable()->after('image')->unique(); + $table->text('sftp_password')->after('image'); + }); + } +} diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php new file mode 100644 index 000000000..53cb6526b --- /dev/null +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -0,0 +1,60 @@ +text('totp_secret')->nullable()->change(); + $table->timestampTz('totp_authenticated_at')->after('totp_secret')->nullable(); + }); + + DB::transaction(function () { + DB::table('users')->get()->each(function ($user) { + if (is_null($user->totp_secret)) { + return; + } + + DB::table('users')->where('id', $user->id)->update([ + 'totp_secret' => Crypt::encrypt($user->totp_secret), + 'updated_at' => Carbon::now()->toIso8601String(), + ]); + }); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + DB::transaction(function () { + DB::table('users')->get()->each(function ($user) { + if (is_null($user->totp_secret)) { + return; + } + + DB::table('users')->where('id', $user->id)->update([ + 'totp_secret' => Crypt::decrypt($user->totp_secret), + 'updated_at' => Carbon::now()->toIso8601String(), + ]); + }); + }); + + DB::statement('ALTER TABLE users MODIFY totp_secret CHAR(16) DEFAULT NULL'); + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('totp_authenticated_at'); + }); + } +} diff --git a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php new file mode 100644 index 000000000..c2947ee07 --- /dev/null +++ b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php @@ -0,0 +1,59 @@ +get()->each(function ($item) { + try { + $decrypted = Crypt::decrypt($item->secret); + } catch (DecryptException $exception) { + $decrypted = str_random(32); + } finally { + DB::table('api_keys')->where('id', $item->id)->update([ + 'secret' => $decrypted, + ]); + } + }); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn('public'); + $table->string('secret', 32)->change(); + }); + + DB::statement('ALTER TABLE `api_keys` CHANGE `secret` `token` CHAR(32) NOT NULL, ADD UNIQUE INDEX `api_keys_token_unique` (`token`(32))'); + } + + /** + * Reverse the migrations. + */ + public function down() + { + DB::statement('ALTER TABLE `api_keys` CHANGE `token` `secret` TEXT, DROP INDEX `api_keys_token_unique`'); + + Schema::table('api_keys', function (Blueprint $table) { + $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), + '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 new file mode 100644 index 000000000..d28109598 --- /dev/null +++ b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['node_id']); + + $table->foreign('node_id')->references('id')->on('nodes')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['node_id']); + + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php new file mode 100644 index 000000000..1bdaf6477 --- /dev/null +++ b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php @@ -0,0 +1,30 @@ +truncate(); + Schema::table('settings', function (Blueprint $table) { + $table->increments('id')->first(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + 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 new file mode 100644 index 000000000..8f9938da1 --- /dev/null +++ b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php @@ -0,0 +1,32 @@ +integer('swap')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + 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 new file mode 100644 index 000000000..cd6b60e10 --- /dev/null +++ b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php @@ -0,0 +1,62 @@ +unsignedTinyInteger('r_servers')->default(0); + $table->unsignedTinyInteger('r_nodes')->default(0); + $table->unsignedTinyInteger('r_allocations')->default(0); + $table->unsignedTinyInteger('r_users')->default(0); + $table->unsignedTinyInteger('r_locations')->default(0); + $table->unsignedTinyInteger('r_nests')->default(0); + $table->unsignedTinyInteger('r_eggs')->default(0); + $table->unsignedTinyInteger('r_database_hosts')->default(0); + $table->unsignedTinyInteger('r_server_databases')->default(0); + $table->unsignedTinyInteger('r_packs')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::create('api_permissions', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('key_id'); + $table->string('permission'); + + $table->foreign('key_id')->references('id')->on('keys')->onDelete('cascade'); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn([ + 'r_servers', + 'r_nodes', + 'r_allocations', + 'r_users', + 'r_locations', + 'r_nests', + 'r_eggs', + 'r_database_hosts', + 'r_server_databases', + 'r_packs', + ]); + }); + } +} diff --git a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php new file mode 100644 index 000000000..e7fd0c58c --- /dev/null +++ b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php @@ -0,0 +1,42 @@ +char('identifier', 16)->unique()->after('user_id'); + $table->dropUnique(['token']); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->text('token')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + * @throws \Exception + * @throws \Throwable + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn('identifier'); + $table->string('token', 32)->unique()->change(); + }); + } +} diff --git a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php new file mode 100644 index 000000000..e0f86b9de --- /dev/null +++ b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php @@ -0,0 +1,46 @@ +unsignedTinyInteger('key_type')->after('user_id')->default(0); + $table->timestamp('last_used_at')->after('memo')->nullable(); + $table->dropColumn('expires_at'); + + $table->dropForeign(['user_id']); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->timestamp('expires_at')->after('memo')->nullable(); + $table->dropColumn('last_used_at', 'key_type'); + $table->dropForeign(['user_id']); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->foreign('user_id')->references('id')->on('users'); + }); + } +} diff --git a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php new file mode 100644 index 000000000..6a4a04e7d --- /dev/null +++ b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php @@ -0,0 +1,32 @@ +string('external_id')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + 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 new file mode 100644 index 000000000..b587cdcb0 --- /dev/null +++ b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php @@ -0,0 +1,32 @@ +dropUnique(['external_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + 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 new file mode 100644 index 000000000..fcf6b4fe3 --- /dev/null +++ b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php @@ -0,0 +1,32 @@ +unique(['allocation_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropUnique(['allocation_id']); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index e6b59672b..fa426deae 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -1,24 +1,15 @@ call(MinecraftServiceTableSeeder::class); - $this->call(SourceServiceTableSeeder::class); - $this->call(TerrariaServiceTableSeeder::class); - $this->call(VoiceServiceTableSeeder::class); - - Model::reguard(); + $this->call(NestSeeder::class); + $this->call(EggSeeder::class); } } diff --git a/database/seeds/EggSeeder.php b/database/seeds/EggSeeder.php new file mode 100644 index 000000000..245d74ac3 --- /dev/null +++ b/database/seeds/EggSeeder.php @@ -0,0 +1,143 @@ +filesystem = $filesystem; + $this->importerService = $importerService; + $this->repository = $repository; + $this->updateImporterService = $updateImporterService; + $this->nestRepository = $nestRepository; + } + + /** + * Run the egg seeder. + */ + public function run() + { + $this->getEggsToImport()->each(function ($nest) { + $this->parseEggFiles($this->findMatchingNest($nest)); + }); + } + + /** + * Return a list of eggs to import. + * + * @return \Illuminate\Support\Collection + */ + protected function getEggsToImport(): Collection + { + return collect([ + 'Minecraft', + 'Source Engine', + 'Voice Servers', + 'Rust', + ]); + } + + /** + * Find the nest that these eggs should be attached to. + * + * @param string $nestName + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function findMatchingNest(string $nestName): Nest + { + return $this->nestRepository->findFirstWhere([ + ['author', '=', 'support@pterodactyl.io'], + ['name', '=', $nestName], + ]); + } + + /** + * Loop through the list of egg files and import them. + * + * @param \Pterodactyl\Models\Nest $nest + */ + private function parseEggFiles(Nest $nest) + { + $files = $this->filesystem->allFiles(database_path('seeds/eggs/' . kebab_case($nest->name))); + + $this->command->alert('Updating Eggs for Nest: ' . $nest->name); + collect($files)->each(function ($file) use ($nest) { + /* @var \Symfony\Component\Finder\SplFileInfo $file */ + $decoded = json_decode($file->getContents()); + if (json_last_error() !== JSON_ERROR_NONE) { + return $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + } + + $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json', $file->getSize()); + + try { + $egg = $this->repository->setColumns('id')->findFirstWhere([ + ['author', '=', $decoded->author], + ['name', '=', $decoded->name], + ['nest_id', '=', $nest->id], + ]); + + $this->updateImporterService->handle($egg->id, $file); + + return $this->command->info('Updated ' . $decoded->name); + } catch (RecordNotFoundException $exception) { + $this->importerService->handle($file, $nest->id); + + return $this->command->comment('Created ' . $decoded->name); + } + }); + + $this->command->line(''); + } +} diff --git a/database/seeds/MinecraftServiceTableSeeder.php b/database/seeds/MinecraftServiceTableSeeder.php deleted file mode 100644 index c2ed986e3..000000000 --- a/database/seeds/MinecraftServiceTableSeeder.php +++ /dev/null @@ -1,375 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; - -class MinecraftServiceTableSeeder extends Seeder -{ - /** - * The core service ID. - * - * @var \Pterodactyl\Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - private $default_mc = <<<'EOF' -'use strict'; - -/** - * Pterodactyl - Daemon - * Copyright (c) 2015 - 2017 Dane Everitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -const rfr = require('rfr'); -const _ = require('lodash'); - -const Core = rfr('src/services/index.js'); - -class Service extends Core { - onConsole(data) { - // Hide the output spam from Bungeecord getting pinged. - if (_.endsWith(data, '<-> InitialHandler has connected')) return; - return super.onConsole(data); - } -} - -module.exports = Service; -EOF; - - /** - * Run the database seeds. - * - * @return void - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'minecraft', - ], [ - 'name' => 'Minecraft', - 'description' => 'Minecraft - the classic game from Mojang. With support for Vanilla MC, Spigot, and many others!', - 'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', - 'index_file' => $this->default_mc, - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# Vanilla MC Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl - -cd /mnt/server - -LATEST_VERSION=`curl -s https://s3.amazonaws.com/Minecraft.Download/versions/versions.json | grep -o "[[:digit:]]\.[0-9]*\.[0-9]" | head -n 1` - -if [ -z "$VANILLA_VERSION" ] || [ "$VANILLA_VERSION" == "latest" ]; then - DL_VERSION=$LATEST_VERSION -else - DL_VERSION=$VANILLA_VERSION -fi - -curl -o ${SERVER_JARFILE} https://s3.amazonaws.com/Minecraft.Download/versions/${DL_VERSION}/minecraft_server.${DL_VERSION}.jar -EOF; - - $this->option['vanilla'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'vanilla', - ], [ - 'name' => 'Vanilla Minecraft', - '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.', - 'docker_image' => 'quay.io/pterodactyl/core:java', - 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', - 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', - 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "enable-query": "true", "server-port": "{{server.build.default.port}}", "query.port": "{{server.build.default.port}}"}}}', - 'config_stop' => 'stop', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Spigot Installation Script -# -# Server Files: /mnt/server - -## Only download if a path is provided, otherwise continue. -if [ ! -z "${DL_PATH}" ]; then - apk update - apk add curl - - cd /mnt/server - - MODIFIED_DOWNLOAD=`eval echo $(echo ${DL_PATH} | sed -e 's/{{/${/g' -e 's/}}/}/g')` - curl -sSL -o ${SERVER_JARFILE} ${MODIFIED_DOWNLOAD} -fi -EOF; - - $this->option['spigot'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'spigot', - ], [ - 'name' => 'Spigot', - 'description' => 'Spigot is the most widely-used modded Minecraft server software in the world. It powers many of the top Minecraft server networks around to ensure they can cope with their huge player base and ensure the satisfaction of their players. Spigot works by reducing and eliminating many causes of lag, as well as adding in handy features and settings that help make your job of server administration easier.', - 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', - 'config_startup' => null, - 'config_files' => '{"spigot.yml":{"parser": "yaml", "find":{"settings.restart-on-crash": false}}}', - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['vanilla']->id, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Sponge Installation Script -# -# Server Files: /mnt/server - -apk update -apk add curl - -cd /mnt/server - -curl -sSL "https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/${SPONGE_VERSION}/spongevanilla-${SPONGE_VERSION}.jar" -o ${SERVER_JARFILE} -EOF; - - $this->option['sponge'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'sponge', - ], [ - 'name' => 'Sponge (SpongeVanilla)', - 'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.', - 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', - 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['vanilla']->id, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Bungeecord Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl - -cd /mnt/server - -if [ -z "${BUNGEE_VERSION}" ] || [ "${BUNGEE_VERSION}" == "latest" ]; then - BUNGEE_VERSION="lastStableBuild" -fi - -curl -o ${SERVER_JARFILE} https://ci.md-5.net/job/BungeeCord/${BUNGEE_VERSION}/artifact/bootstrap/target/BungeeCord.jar -EOF; - - $this->option['bungeecord'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'bungeecord', - ], [ - 'name' => 'Bungeecord', - '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.', - 'docker_image' => 'quay.io/pterodactyl/core:java', - 'config_startup' => '{"done": "Listening on ", "userInteraction": [ "Listening on /0.0.0.0:25577"]}', - 'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].query_enabled": true, "listeners[0].query_port": "{{server.build.default.port}}", "listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}', - 'config_logs' => '{"custom": false, "location": "proxy.log.0"}', - 'config_stop' => 'end', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - ]); - } - - private function addVariables() - { - $this->addVanillaVariables(); - $this->addSpigotVariables(); - $this->addSpongeVariables(); - $this->addBungeecordVariables(); - } - - private function addVanillaVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['vanilla']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the server jarfile to run the server with.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['vanilla']->id, - 'env_variable' => 'VANILLA_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|between:3,7', - ]); - } - - private function addSpigotVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the server jarfile to run the server with.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'DL_VERSION', - ], [ - 'name' => 'Spigot Version', - 'description' => 'The version of Spigot to download (using the --rev tag). Use "latest" for latest.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|between:3,7', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'DL_PATH', - ], [ - 'name' => 'Download Path', - 'description' => 'A URL to use to download Spigot rather than building it on the server. This is not user viewable. Use {{DL_VERSION}} in the URL to automatically insert the assigned version into the URL. If you do not enter a URL Spigot will build directly in the container (this will fail on low memory containers).', - 'default_value' => '', - 'user_viewable' => 0, - 'user_editable' => 0, - 'rules' => 'string', - ]); - } - - private function addSpongeVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['sponge']->id, - 'env_variable' => 'SPONGE_VERSION', - ], [ - 'name' => 'Sponge Version', - 'description' => 'The version of SpongeVanilla to download and use.', - 'default_value' => '1.10.2-5.2.0-BETA-381', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^([a-zA-Z0-9.\-_]+)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['sponge']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the Jarfile to use when running SpongeVanilla.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - } - - private function addBungeecordVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['bungeecord']->id, - 'env_variable' => 'BUNGEE_VERSION', - ], [ - 'name' => 'Bungeecord Version', - 'description' => 'The version of Bungeecord to download and use.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|alpha_num|between:1,6', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['bungeecord']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Bungeecord Jar File', - 'description' => 'The name of the Jarfile to use when running Bungeecord.', - 'default_value' => 'bungeecord.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - } -} diff --git a/database/seeds/NestSeeder.php b/database/seeds/NestSeeder.php new file mode 100644 index 000000000..38b221846 --- /dev/null +++ b/database/seeds/NestSeeder.php @@ -0,0 +1,117 @@ +creationService = $creationService; + $this->repository = $repository; + } + + /** + * Run the seeder to add missing nests to the Panel. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function run() + { + $items = $this->repository->findWhere([ + 'author' => 'support@pterodactyl.io', + ])->keyBy('name')->toArray(); + + $this->createMinecraftNest(array_get($items, 'Minecraft')); + $this->createSourceEngineNest(array_get($items, 'Source Engine')); + $this->createVoiceServersNest(array_get($items, 'Voice Servers')); + $this->createRustNest(array_get($items, 'Rust')); + } + + /** + * Create the Minecraft nest to be used later on. + * + * @param array|null $nest + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + private function createMinecraftNest(array $nest = null) + { + if (is_null($nest)) { + $this->creationService->handle([ + 'name' => 'Minecraft', + 'description' => 'Minecraft - the classic game from Mojang. With support for Vanilla MC, Spigot, and many others!', + ], 'support@pterodactyl.io'); + } + } + + /** + * Create the Source Engine Games nest to be used later on. + * + * @param array|null $nest + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + private function createSourceEngineNest(array $nest = null) + { + if (is_null($nest)) { + $this->creationService->handle([ + 'name' => 'Source Engine', + 'description' => 'Includes support for most Source Dedicated Server games.', + ], 'support@pterodactyl.io'); + } + } + + /** + * Create the Voice Servers nest to be used later on. + * + * @param array|null $nest + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + private function createVoiceServersNest(array $nest = null) + { + if (is_null($nest)) { + $this->creationService->handle([ + 'name' => 'Voice Servers', + 'description' => 'Voice servers such as Mumble and Teamspeak 3.', + ], 'support@pterodactyl.io'); + } + } + + /** + * Create the Rust nest to be used later on. + * + * @param array|null $nest + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + private function createRustNest(array $nest = null) + { + if (is_null($nest)) { + $this->creationService->handle([ + 'name' => 'Rust', + 'description' => 'Rust - A game where you must fight to survive.', + ], 'support@pterodactyl.io'); + } + } +} diff --git a/database/seeds/SourceServiceTableSeeder.php b/database/seeds/SourceServiceTableSeeder.php deleted file mode 100644 index 171aebecc..000000000 --- a/database/seeds/SourceServiceTableSeeder.php +++ /dev/null @@ -1,348 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; - -class SourceServiceTableSeeder extends Seeder -{ - /** - * The core service ID. - * - * @var Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - * - * @return void - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'srcds', - ], [ - 'name' => 'Source Engine', - 'description' => 'Includes support for most Source Dedicated Server games.', - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +ip 0.0.0.0 -strictportbind -norestart', - 'index_file' => Service::defaultIndexFile(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/bash -# SRCDS Base Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update ${SRCDS_APPID} +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['source'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'source', - ], [ - 'name' => 'Custom Source Engine Game', - 'description' => 'This option allows modifying the startup arguments and other details to run a custo SRCDS based game on the panel.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "gameserver Steam ID", "userInteraction": []}', - 'config_files' => '{}', - 'config_logs' => '{"custom": true, "location": "logs/latest.log"}', - 'config_stop' => 'quit', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - - $this->option['insurgency'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'insurgency', - ], [ - 'name' => 'Insurgency', - 'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => null, - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', - 'copy_script_from' => $this->option['source']->id, - ]); - - $this->option['tf2'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'tf2', - ], [ - 'name' => 'Team Fortress 2', - 'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => null, - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', - 'copy_script_from' => $this->option['source']->id, - ]); - - $script = <<<'EOF' -#!/bin/bash -# ARK: Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -mkdir -p /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux - -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux - -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 376030 +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['ark'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'ark', - ], [ - 'name' => 'Ark: Survival Evolved', - 'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "Setting breakpad minidump AppID"}', - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => '^C', - 'config_from' => $this->option['source']->id, - 'startup' => './ShooterGame/Binaries/Linux/ShooterGameServer TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}', - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - } - - private function addVariables() - { - $this->addInsurgencyVariables(); - $this->addTF2Variables(); - $this->addArkVariables(); - $this->addCustomVariables(); - } - - private function addInsurgencyVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '17705', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(17705)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => 'insurgency', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(insurgency)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Default Map', - 'description' => 'The default map to use when starting the server.', - 'default_value' => 'sinjar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^(\w{1,20})$/', - ]); - } - - private function addTF2Variables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '232250', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(232250)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => 'tf', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(tf)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Default Map', - 'description' => 'The default map to use when starting the server.', - 'default_value' => 'cp_dustbowl', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^(\w{1,20})$/', - ]); - } - - private function addArkVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'ARK_PASSWORD', - ], [ - 'name' => 'Server Password', - 'description' => 'If specified, players must provide this password to join the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'alpha_dash|between:1,100', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'ARK_ADMIN_PASSWORD', - ], [ - 'name' => 'Admin Password', - 'description' => 'If specified, players must provide this password (via the in-game console) to gain access to administrator commands on the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'alpha_dash|between:1,100', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'SERVER_MAX_PLAYERS', - ], [ - 'name' => 'Maximum Players', - 'description' => 'Specifies the maximum number of players that can play on the server simultaneously.', - 'default_value' => 20, - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|numeric|digits_between:1,4', - ]); - } - - private function addCustomVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['source']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,6', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['source']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|alpha_dash|between:1,100', - ]); - } -} diff --git a/database/seeds/TerrariaServiceTableSeeder.php b/database/seeds/TerrariaServiceTableSeeder.php deleted file mode 100644 index 6d451f12b..000000000 --- a/database/seeds/TerrariaServiceTableSeeder.php +++ /dev/null @@ -1,130 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; - -class TerrariaServiceTableSeeder extends Seeder -{ - /** - * The core service ID. - * - * @var Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - * - * @return void - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'terraria', - ], [ - 'name' => 'Terraria', - 'description' => 'Terraria is a land of adventure! A land of mystery! A land that\'s yours to shape, defend, and enjoy. Your options in Terraria are limitless. Are you an action gamer with an itchy trigger finger? A master builder? A collector? An explorer? There\'s something for everyone.', - 'startup' => 'mono TerrariaServer.exe -port {{SERVER_PORT}} -autocreate 2 -worldname World', - 'index_file' => Service::defaultIndexFile(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# TShock Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl unzip - -cd /tmp - -curl -sSLO https://github.com/NyxStudios/TShock/releases/download/v${T_VERSION}/tshock_${T_VERSION}.zip - -unzip -o tshock_${T_VERSION}.zip -d /mnt/server -EOF; - - $this->option['tshock'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'tshock', - ], [ - 'name' => 'Terraria Server (TShock)', - 'description' => 'TShock is a server modification for Terraria, written in C#, and based upon the Terraria Server API. It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.', - 'docker_image' => 'quay.io/pterodactyl/core:mono', - 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', - 'config_startup' => '{"done": "Type \'help\' for a list of commands", "userInteraction": []}', - 'config_files' => '{"tshock/config.json":{"parser": "json", "find":{"ServerPort": "{{server.build.default.port}}", "MaxSlots": "{{server.build.env.MAX_SLOTS}}"}}}', - 'config_logs' => '{"custom": false, "location": "ServerLog.txt"}', - 'config_stop' => 'exit', - 'startup' => null, - 'script_install' => $script, - ]); - } - - private function addVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tshock']->id, - 'env_variable' => 'T_VERSION', - ], [ - 'name' => 'TShock Version', - 'description' => 'Which version of TShock to install and use.', - 'default_value' => '4.3.22', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tshock']->id, - 'env_variable' => 'MAX_SLOTS', - ], [ - 'name' => 'Maximum Slots', - 'description' => 'Total number of slots to allow on the server.', - 'default_value' => 20, - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,3', - ]); - } -} diff --git a/database/seeds/VoiceServiceTableSeeder.php b/database/seeds/VoiceServiceTableSeeder.php deleted file mode 100644 index 010302c95..000000000 --- a/database/seeds/VoiceServiceTableSeeder.php +++ /dev/null @@ -1,196 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; - -class VoiceServiceTableSeeder extends Seeder -{ - /** - * The core service ID. - * - * @var Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - * - * @return void - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'voice', - ], [ - 'name' => 'Voice Servers', - 'description' => 'Voice servers such as Mumble and Teamspeak 3.', - 'startup' => '', - 'index_file' => Service::defaultIndexFile(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# Mumble Installation Script -# -# Server Files: /mnt/server -apk update -apk add tar curl - -cd /tmp - -curl -sSLO https://github.com/mumble-voip/mumble/releases/download/${MUMBLE_VERSION}/murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 - -tar -xjvf murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 -cp -r murmur-static_x86-${MUMBLE_VERSION}/* /mnt/server -EOF; - - $this->option['mumble'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'mumble', - ], [ - 'name' => 'Mumble Server', - 'description' => 'Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.', - 'docker_image' => 'quay.io/pterodactyl/core:glibc', - 'config_startup' => '{"done": "Server listening on", "userInteraction": [ "Generating new server certificate"]}', - 'config_files' => '{"murmur.ini":{"parser": "ini", "find":{"logfile": "murmur.log", "port": "{{server.build.default.port}}", "host": "0.0.0.0", "users": "{{server.build.env.MAX_USERS}}"}}}', - 'config_logs' => '{"custom": true, "location": "logs/murmur.log"}', - 'config_stop' => '^C', - 'config_from' => null, - 'startup' => './murmur.x86 -fg', - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# TS3 Installation Script -# -# Server Files: /mnt/server -apk update -apk add tar curl - -cd /tmp - -curl -sSLO http://dl.4players.de/ts/releases/${TS_VERSION}/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 - -tar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 -cp -r teamspeak3-server_linux_amd64/* /mnt/server - -echo "machine_id= -default_voice_port=${SERVER_PORT} -voice_ip=0.0.0.0 -licensepath= -filetransfer_port=30033 -filetransfer_ip= -query_port=${SERVER_PORT} -query_ip=0.0.0.0 -query_ip_whitelist=query_ip_whitelist.txt -query_ip_blacklist=query_ip_blacklist.txt -dbplugin=ts3db_sqlite3 -dbpluginparameter= -dbsqlpath=sql/ -dbsqlcreatepath=create_sqlite/ -dbconnections=10 -logpath=logs -logquerycommands=0 -dbclientkeepdays=30 -logappend=0 -query_skipbruteforcecheck=0" > /mnt/server/ts3server.ini -EOF; - - $this->option['ts3'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'ts3', - ], [ - 'name' => 'Teamspeak3 Server', - 'description' => 'VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.', - 'docker_image' => 'quay.io/pterodactyl/core:glibc', - 'config_startup' => '{"done": "listening on 0.0.0.0:", "userInteraction": []}', - 'config_files' => '{"ts3server.ini":{"parser": "ini", "find":{"default_voice_port": "{{server.build.default.port}}", "voice_ip": "0.0.0.0", "query_port": "{{server.build.default.port}}", "query_ip": "0.0.0.0"}}}', - 'config_logs' => '{"custom": true, "location": "logs/ts3.log"}', - 'config_stop' => '^C', - 'config_from' => null, - 'startup' => './ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}', - 'script_install' => $script, - ]); - } - - private function addVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['mumble']->id, - 'env_variable' => 'MAX_USERS', - ], [ - 'name' => 'Maximum Users', - 'description' => 'Maximum concurrent users on the mumble server.', - 'default_value' => 100, - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,5', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['mumble']->id, - 'env_variable' => 'MUMBLE_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'Version of Mumble Server to download and use.', - 'default_value' => '1.2.19', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,8})$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ts3']->id, - 'env_variable' => 'TS_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'The version of Teamspeak 3 to use when running the server.', - 'default_value' => '3.0.13.6', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', - ]); - } -} diff --git a/database/seeds/eggs/minecraft/egg-bungeecord.json b/database/seeds/eggs/minecraft/egg-bungeecord.json new file mode 100644 index 000000000..1f60064ac --- /dev/null +++ b/database/seeds/eggs/minecraft/egg-bungeecord.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2017-11-03T22:15:10-05: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.", + "image": "quay.io\/pterodactyl\/core:java", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "config": { + "files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_enabled\": true,\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"127.0.0.1\": \"{{config.docker.interface}}\",\r\n \"localhost\": \"{{config.docker.interface}}\"\r\n }\r\n }\r\n }\r\n}", + "startup": "{\r\n \"done\": \"Listening on \",\r\n \"userInteraction\": [\r\n \"Listening on \/0.0.0.0:25577\"\r\n ]\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}", + "stop": "end" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# Bungeecord Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add curl\n\ncd \/mnt\/server\n\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\n BUNGEE_VERSION=\"lastStableBuild\"\nfi\n\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Bungeecord Version", + "description": "The version of Bungeecord to download and use.", + "env_variable": "BUNGEE_VERSION", + "default_value": "latest", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|alpha_num|between:1,6" + }, + { + "name": "Bungeecord Jar File", + "description": "The name of the Jarfile to use when running Bungeecord.", + "env_variable": "SERVER_JARFILE", + "default_value": "bungeecord.jar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/minecraft/egg-forge-minecraft.json b/database/seeds/eggs/minecraft/egg-forge-minecraft.json new file mode 100644 index 000000000..a4ab2c4b9 --- /dev/null +++ b/database/seeds/eggs/minecraft/egg-forge-minecraft.json @@ -0,0 +1,36 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2017-11-03T22:15:10-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.", + "image": "quay.io\/pterodactyl\/core:java", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "config": { + "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", + "startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "stop" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# Forge Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add curl\n\nGET_VERSIONS=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\.[0-9][0-9][0-9][0-9]')\nLATEST_VERSION=$(echo $GET_VERSIONS | sed 's\/ \/\/g')\n\ncd \/mnt\/server\n\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-installer.jar -o installer.jar\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-universal.jar -o server.jar\n\njava -jar installer.jar --installServer\nrm -rf installer.jar", + "container": "frolvlad\/alpine-oraclejdk8:cleaned", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Server Jar File", + "description": "The name of the Jarfile to use when running Forge Mod.", + "env_variable": "SERVER_JARFILE", + "default_value": "server.jar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/minecraft/egg-spigot.json b/database/seeds/eggs/minecraft/egg-spigot.json new file mode 100644 index 000000000..4de4c6e6d --- /dev/null +++ b/database/seeds/eggs/minecraft/egg-spigot.json @@ -0,0 +1,54 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:47:24-06:00", + "name": "Spigot", + "author": "support@pterodactyl.io", + "description": "Spigot is the most widely-used modded Minecraft server software in the world. It powers many of the top Minecraft server networks around to ensure they can cope with their huge player base and ensure the satisfaction of their players. Spigot works by reducing and eliminating many causes of lag, as well as adding in handy features and settings that help make your job of server administration easier.", + "image": "quay.io\/pterodactyl\/core:java-glibc", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "config": { + "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", + "startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "stop" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# Spigot Installation Script\n#\n# Server Files: \/mnt\/server\n\n## Only download if a path is provided, otherwise continue.\nif [ ! -z \"${DL_PATH}\" ]; then\n apk update\n apk add curl\n\n cd \/mnt\/server\n\n MODIFIED_DOWNLOAD=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\n curl -sSL -o ${SERVER_JARFILE} ${MODIFIED_DOWNLOAD}\nfi", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Server Jar File", + "description": "The name of the server jarfile to run the server with.", + "env_variable": "SERVER_JARFILE", + "default_value": "server.jar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + }, + { + "name": "Spigot Version", + "description": "The version of Spigot to download (using the --rev tag). Use \"latest\" for latest.", + "env_variable": "DL_VERSION", + "default_value": "latest", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|between:3,7" + }, + { + "name": "Download Path", + "description": "A URL to use to download Spigot rather than building it on the server. This is not user viewable. Use {{DL_VERSION}}<\/code> in the URL to automatically insert the assigned version into the URL. If you do not enter a URL Spigot will build directly in the container (this will fail on low memory containers).", + "env_variable": "DL_PATH", + "default_value": "", + "user_viewable": 0, + "user_editable": 0, + "rules": "nullable|string" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/seeds/eggs/minecraft/egg-sponge--sponge-vanilla.json new file mode 100644 index 000000000..489100284 --- /dev/null +++ b/database/seeds/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2017-11-03T22:20:03-05:00", + "name": "Sponge (SpongeVanilla)", + "author": "support@pterodactyl.io", + "description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.", + "image": "quay.io\/pterodactyl\/core:java-glibc", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "config": { + "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", + "startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "stop" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# Sponge Installation Script\n#\n# Server Files: \/mnt\/server\n\napk update\napk add curl\n\ncd \/mnt\/server\n\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Sponge Version", + "description": "The version of SpongeVanilla to download and use.", + "env_variable": "SPONGE_VERSION", + "default_value": "1.11.2-6.1.0-BETA-21", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/" + }, + { + "name": "Server Jar File", + "description": "The name of the Jarfile to use when running SpongeVanilla.", + "env_variable": "SERVER_JARFILE", + "default_value": "server.jar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json b/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json new file mode 100644 index 000000000..3d87b1fa2 --- /dev/null +++ b/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2017-11-03T22:15:07-05: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.", + "image": "quay.io\/pterodactyl\/core:java", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "config": { + "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", + "startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "stop" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl -s https:\/\/s3.amazonaws.com\/Minecraft.Download\/versions\/versions.json | grep -o \"[[:digit:]]\\.[0-9]*\\.[0-9]\" | head -n 1`\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n DL_VERSION=$LATEST_VERSION\r\nelse\r\n DL_VERSION=$VANILLA_VERSION\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/s3.amazonaws.com\/Minecraft.Download\/versions\/${DL_VERSION}\/minecraft_server.${DL_VERSION}.jar", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Server Jar File", + "description": "The name of the server jarfile to run the server with.", + "env_variable": "SERVER_JARFILE", + "default_value": "server.jar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + }, + { + "name": "Server Version", + "description": "The version of Minecraft Vanilla to install. Use \"latest\" to install the latest version.", + "env_variable": "VANILLA_VERSION", + "default_value": "latest", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|between:3,7" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/rust/egg-rust.json b/database/seeds/eggs/rust/egg-rust.json new file mode 100644 index 000000000..af9d0c18c --- /dev/null +++ b/database/seeds/eggs/rust/egg-rust.json @@ -0,0 +1,135 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:58:36-06:00", + "name": "Rust", + "author": "support@pterodactyl.io", + "description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.", + "image": "quay.io\/pterodactyl\/core:rust", + "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" {{ADDITIONAL_ARGS}}", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"Server startup complete\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": false,\r\n \"location\": \"latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "apt update\r\napt -y --no-install-recommends install curl unzip lib32gcc1 ca-certificates\r\ncd \/tmp\r\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\r\n\r\nmkdir -p \/mnt\/server\/steam\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steam\r\ncd \/mnt\/server\/steam\r\nchown -R root:root \/mnt\r\n\r\nexport HOME=\/mnt\/server\r\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 258550 +quit\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v \/mnt\/server\/steam\/linux32\/steamclient.so \/mnt\/server\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Server Name", + "description": "The name of your server in the public server list.", + "env_variable": "HOSTNAME", + "default_value": "A Rust Server", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:40" + }, + { + "name": "OxideMod", + "description": "Set whether you want the server to use and auto update OxideMod or not. Valid options are \"1\" for true and \"0\" for false.", + "env_variable": "OXIDE", + "default_value": "0", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|boolean" + }, + { + "name": "Level", + "description": "The world file for Rust to use.", + "env_variable": "LEVEL", + "default_value": "Procedural Map", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:20" + }, + { + "name": "Description", + "description": "The description under your server title. Commonly used for rules & info. Use \\n for newlines.", + "env_variable": "DESCRIPTION", + "default_value": "Powered by Pterodactyl", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string" + }, + { + "name": "URL", + "description": "The URL for your server. This is what comes up when clicking the \"Visit Website\" button.", + "env_variable": "SERVER_URL", + "default_value": "http:\/\/pterodactyl.io", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|url" + }, + { + "name": "World Size", + "description": "The world size for a procedural map.", + "env_variable": "WORLD_SIZE", + "default_value": "3000", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|integer" + }, + { + "name": "World Seed", + "description": "The seed for a procedural map.", + "env_variable": "WORLD_SEED", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|string" + }, + { + "name": "Max Players", + "description": "The maximum amount of players allowed in the server at once.", + "env_variable": "MAX_PLAYERS", + "default_value": "40", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|integer" + }, + { + "name": "Server Image", + "description": "The header image for the top of your server listing.", + "env_variable": "SERVER_IMG", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|url" + }, + { + "name": "RCON Port", + "description": "Port for RCON connections.", + "env_variable": "RCON_PORT", + "default_value": "8401", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|integer" + }, + { + "name": "RCON Password", + "description": "RCON access password.", + "env_variable": "RCON_PASS", + "default_value": "CHANGEME", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:64" + }, + { + "name": "Additional Arguments", + "description": "Add additional startup parameters to the server.", + "env_variable": "ADDITIONAL_ARGS", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|string" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json b/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json new file mode 100644 index 000000000..092033092 --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json @@ -0,0 +1,54 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:40-06:00", + "name": "Ark: Survival Evolved", + "author": "support@pterodactyl.io", + "description": "As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! \u2014 Gamepedia: ARK", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/ShooterGame\/Binaries\/Linux\/ShooterGameServer TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# ARK: Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\nmkdir -p \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\n\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\n\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 376030 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Server Password", + "description": "If specified, players must provide this password to join the server.", + "env_variable": "ARK_PASSWORD", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|alpha_dash|between:1,100" + }, + { + "name": "Admin Password", + "description": "If specified, players must provide this password (via the in-game console) to gain access to administrator commands on the server.", + "env_variable": "ARK_ADMIN_PASSWORD", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "nullable|alpha_dash|between:1,100" + }, + { + "name": "Maximum Players", + "description": "Specifies the maximum number of players that can play on the server simultaneously.", + "env_variable": "SERVER_MAX_PLAYERS", + "default_value": "20", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|numeric|digits_between:1,4" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json b/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json new file mode 100644 index 000000000..3c1ae8e56 --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:47-06:00", + "name": "Counter-Strike: Global Offensive", + "author": "support@pterodactyl.io", + "description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# CSGO Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 740 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Map", + "description": "The default map for the server.", + "env_variable": "SRCDS_MAP", + "default_value": "de_dust2", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|alpha_dash" + }, + { + "name": "Steam Account Token", + "description": "The Steam Account Token required for the server to be displayed publicly.", + "env_variable": "STEAM_ACC", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|alpha_num|size:32" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json b/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json new file mode 100644 index 000000000..7a7b40bd0 --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json @@ -0,0 +1,54 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:45-06:00", + "name": "Custom Source Engine Game", + "author": "support@pterodactyl.io", + "description": "This option allows modifying the startup arguments and other details to run a custom SRCDS based game on the panel.", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Game ID", + "description": "The ID corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_APPID", + "default_value": "", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|numeric|digits_between:1,6" + }, + { + "name": "Game Name", + "description": "The name corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_GAME", + "default_value": "", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|alpha_dash|between:1,100" + }, + { + "name": "Map", + "description": "The default map for the server.", + "env_variable": "SRCDS_MAP", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|alpha_dash" + } + ] +} diff --git a/database/seeds/eggs/source-engine/egg-garrys-mod.json b/database/seeds/eggs/source-engine/egg-garrys-mod.json new file mode 100644 index 000000000..c8f3ebb84 --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-garrys-mod.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:47-06:00", + "name": "Garrys Mod", + "author": "support@pterodactyl.io", + "description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# Garry's Mod Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 4020 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Map", + "description": "The default map for the server.", + "env_variable": "SRCDS_MAP", + "default_value": "gm_flatgrass", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|alpha_dash" + }, + { + "name": "Steam Account Token", + "description": "The Steam Account Token required for the server to be displayed publicly.", + "env_variable": "STEAM_ACC", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|alpha_num|size:32" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-insurgency.json b/database/seeds/eggs/source-engine/egg-insurgency.json new file mode 100644 index 000000000..4e3df4a8e --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-insurgency.json @@ -0,0 +1,54 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:48-06:00", + "name": "Insurgency", + "author": "support@pterodactyl.io", + "description": "Take to the streets for intense close quarters combat, where a team's survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", + "config": { + "files": "{}", + "startup": "{\"done\": \"gameserver Steam ID\", \"userInteraction\": []}", + "logs": "{\"custom\": true, \"location\": \"logs\/latest.log\"}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Game ID", + "description": "The ID corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_APPID", + "default_value": "17705", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|regex:\/^(17705)$\/" + }, + { + "name": "Game Name", + "description": "The name corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_GAME", + "default_value": "insurgency", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|regex:\/^(insurgency)$\/" + }, + { + "name": "Default Map", + "description": "The default map to use when starting the server.", + "env_variable": "SRCDS_MAP", + "default_value": "sinjar", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^(\\w{1,20})$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-team-fortress2.json b/database/seeds/eggs/source-engine/egg-team-fortress2.json new file mode 100644 index 000000000..ae443370c --- /dev/null +++ b/database/seeds/eggs/source-engine/egg-team-fortress2.json @@ -0,0 +1,54 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T16:59:45-06:00", + "name": "Team Fortress 2", + "author": "support@pterodactyl.io", + "description": "Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.", + "image": "quay.io\/pterodactyl\/core:source", + "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}", + "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "stop": "quit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "container": "ubuntu:16.04", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Game ID", + "description": "The ID corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_APPID", + "default_value": "232250", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|regex:\/^(232250)$\/" + }, + { + "name": "Game Name", + "description": "The name corresponding to the game to download and run using SRCDS.", + "env_variable": "SRCDS_GAME", + "default_value": "tf", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|regex:\/^(tf)$\/" + }, + { + "name": "Default Map", + "description": "The default map to use when starting the server.", + "env_variable": "SRCDS_MAP", + "default_value": "cp_dustbowl", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^(\\w{1,20})$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/terraria/egg-terraria-server--t-shock.json b/database/seeds/eggs/terraria/egg-terraria-server--t-shock.json new file mode 100644 index 000000000..9be0f1142 --- /dev/null +++ b/database/seeds/eggs/terraria/egg-terraria-server--t-shock.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T17:01:31-06:00", + "name": "Terraria Server (TShock)", + "author": "support@pterodactyl.io", + "description": "TShock is a server modification for Terraria, written in C#, and based upon the Terraria Server API. It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.", + "image": "quay.io\/pterodactyl\/core:mono", + "startup": null, + "config": { + "files": "{\"tshock\/config.json\":{\"parser\": \"json\", \"find\":{\"ServerPort\": \"{{server.build.default.port}}\", \"MaxSlots\": \"{{server.build.env.MAX_SLOTS}}\"}}}", + "startup": "{\"done\": \"Type 'help' for a list of commands\", \"userInteraction\": []}", + "logs": "{\"custom\": false, \"location\": \"ServerLog.txt\"}", + "stop": "exit" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# TShock Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add curl unzip\n\ncd \/tmp\n\ncurl -sSLO https:\/\/github.com\/NyxStudios\/TShock\/releases\/download\/v${T_VERSION}\/tshock_${T_VERSION}.zip\n\nunzip -o tshock_${T_VERSION}.zip -d \/mnt\/server", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "TShock Version", + "description": "Which version of TShock to install and use.", + "env_variable": "T_VERSION", + "default_value": "4.3.22", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([0-9_\\.-]{5,10})$\/" + }, + { + "name": "Maximum Slots", + "description": "Total number of slots to allow on the server.", + "env_variable": "MAX_SLOTS", + "default_value": "20", + "user_viewable": 1, + "user_editable": 0, + "rules": "required|numeric|digits_between:1,3" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/voice-servers/egg-mumble-server.json b/database/seeds/eggs/voice-servers/egg-mumble-server.json new file mode 100644 index 000000000..5e6a98e17 --- /dev/null +++ b/database/seeds/eggs/voice-servers/egg-mumble-server.json @@ -0,0 +1,45 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T17:01:44-06: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.", + "image": "quay.io\/pterodactyl\/core:glibc", + "startup": ".\/murmur.x86 -fg", + "config": { + "files": "{\"murmur.ini\":{\"parser\": \"ini\", \"find\":{\"logfile\": \"murmur.log\", \"port\": \"{{server.build.default.port}}\", \"host\": \"0.0.0.0\", \"users\": \"{{server.build.env.MAX_USERS}}\"}}}", + "startup": "{\"done\": \"Server listening on\", \"userInteraction\": [ \"Generating new server certificate\"]}", + "logs": "{\"custom\": true, \"location\": \"logs\/murmur.log\"}", + "stop": "^C" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# Mumble Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add tar curl\n\ncd \/tmp\n\ncurl -sSLO https:\/\/github.com\/mumble-voip\/mumble\/releases\/download\/${MUMBLE_VERSION}\/murmur-static_x86-${MUMBLE_VERSION}.tar.bz2\n\ntar -xjvf murmur-static_x86-${MUMBLE_VERSION}.tar.bz2\ncp -r murmur-static_x86-${MUMBLE_VERSION}\/* \/mnt\/server", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Maximum Users", + "description": "Maximum concurrent users on the mumble server.", + "env_variable": "MAX_USERS", + "default_value": "100", + "user_viewable": 1, + "user_editable": 0, + "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": "1.2.19", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([0-9_\\.-]{5,8})$\/" + } + ] +} \ No newline at end of file diff --git a/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json b/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json new file mode 100644 index 000000000..fe73a1665 --- /dev/null +++ b/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json @@ -0,0 +1,36 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v1" + }, + "exported_at": "2018-01-21T17:01:45-06:00", + "name": "Teamspeak3 Server", + "author": "support@pterodactyl.io", + "description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.", + "image": "quay.io\/pterodactyl\/core:glibc", + "startup": ".\/ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}", + "config": { + "files": "{\"ts3server.ini\":{\"parser\": \"ini\", \"find\":{\"default_voice_port\": \"{{server.build.default.port}}\", \"voice_ip\": \"0.0.0.0\", \"query_port\": \"{{server.build.default.port}}\", \"query_ip\": \"0.0.0.0\"}}}", + "startup": "{\"done\": \"listening on 0.0.0.0:\", \"userInteraction\": []}", + "logs": "{\"custom\": true, \"location\": \"logs\/ts3.log\"}", + "stop": "^C" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/ash\n# TS3 Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add tar curl\n\ncd \/tmp\n\ncurl -sSLO http:\/\/dl.4players.de\/ts\/releases\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\n\ntar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\ncp -r teamspeak3-server_linux_amd64\/* \/mnt\/server\n\necho \"machine_id=\ndefault_voice_port=${SERVER_PORT}\nvoice_ip=0.0.0.0\nlicensepath=\nfiletransfer_port=30033\nfiletransfer_ip=\nquery_port=${SERVER_PORT}\nquery_ip=0.0.0.0\nquery_ip_whitelist=query_ip_whitelist.txt\nquery_ip_blacklist=query_ip_blacklist.txt\ndbplugin=ts3db_sqlite3\ndbpluginparameter=\ndbsqlpath=sql\/\ndbsqlcreatepath=create_sqlite\/\ndbconnections=10\nlogpath=logs\nlogquerycommands=0\ndbclientkeepdays=30\nlogappend=0\nquery_skipbruteforcecheck=0\" > \/mnt\/server\/ts3server.ini", + "container": "alpine:3.4", + "entrypoint": "ash" + } + }, + "variables": [ + { + "name": "Server Version", + "description": "The version of Teamspeak 3 to use when running the server.", + "env_variable": "TS_VERSION", + "default_value": "3.0.13.8", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|regex:\/^([0-9_\\.-]{5,10})$\/" + } + ] +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 000000000..82cc1fd74 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + ./tests/Feature + + + ./tests/Unit + + + + + ./app + + + + + + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess index 342448645..b75525bed 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,17 +1,18 @@ - Options -MultiViews + Options -MultiViews -Indexes RewriteEngine On + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^(.*)/$ /$1 [L,R=301] - - # Prevent stripping authorization headers - RewriteCond %{HTTP:Authorization} ^(.*) - RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] # Handle Front Controller... RewriteCond %{REQUEST_FILENAME} !-d diff --git a/public/index.php b/public/index.php index f2bd8213e..233cba743 100644 --- a/public/index.php +++ b/public/index.php @@ -3,8 +3,9 @@ /** * Laravel - A PHP Framework For Web Artisans. * - * @author Taylor Otwell + * @author Taylor Otwell */ +define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- @@ -14,11 +15,11 @@ | Composer provides a convenient, automatically generated class loader for | our application. We just need to utilize it! We'll simply require it | into the script here so that we don't have to worry about manual -| loading any of our classes later on. It feels nice to relax. +| loading any of our classes later on. It feels great to relax. | */ -require __DIR__ . '/../bootstrap/autoload.php'; +require __DIR__ . '/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- diff --git a/public/js/laroute.js b/public/js/laroute.js index 10af670a1..bd13dfa2f 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -5,8 +5,8 @@ var routes = { absolute: false, - rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"api\/user","name":"api.user","action":"Pterodactyl\Http\Controllers\API\User\CoreController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/user\/server\/{server}","name":"api.user.server","action":"Pterodactyl\Http\Controllers\API\User\ServerController@index"},{"host":null,"methods":["POST"],"uri":"api\/user\/server\/{server}\/power","name":"api.user.server.power","action":"Pterodactyl\Http\Controllers\API\User\ServerController@power"},{"host":null,"methods":["POST"],"uri":"api\/user\/server\/{server}\/command","name":"api.user.server.command","action":"Pterodactyl\Http\Controllers\API\User\ServerController@command"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\CoreController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/servers","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/servers\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/servers","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@store"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@details"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/container","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@container"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@build"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@startup"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/install","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@install"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/rebuild","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@rebuild"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/suspend","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@suspend"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/servers\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{id}\/config","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@viewConfig"},{"host":null,"methods":["POST"],"uri":"api\/admin\/nodes","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@store"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/users","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@store"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/services","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServiceController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/services\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServiceController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"index","name":null,"action":"Closure"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{id}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["POST"],"uri":"admin\/locations\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{id}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["POST"],"uri":"admin\/databases\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{id}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["POST"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{id}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{id}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/view\/{id}","name":"admin.services.view","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/view\/{id}\/functions","name":"admin.services.view.functions","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@viewFunctions"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\OptionController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{id}","name":"admin.services.option.view","action":"Pterodactyl\Http\Controllers\Admin\OptionController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{id}\/variables","name":"admin.services.option.variables","action":"Pterodactyl\Http\Controllers\Admin\OptionController@viewVariables"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{id}\/scripts","name":"admin.services.option.scripts","action":"Pterodactyl\Http\Controllers\Admin\OptionController@viewScripts"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@store"},{"host":null,"methods":["POST"],"uri":"admin\/services\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@edit"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@store"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@editConfiguration"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/{id}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@updateScripts"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/{id}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@createVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/{id}\/variables\/{variable}","name":"admin.services.option.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\OptionController@editVariable"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{id}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{id}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ServerController@getConsole"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/databases","name":"server.settings.databases","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDatabases"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getFiles"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAddFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\ServerController@getEditFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDownloadFile"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postDirectoryList"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSaveFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{id}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/delete\/{id}","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks","name":"server.tasks","action":"Pterodactyl\Http\Controllers\Server\TaskController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/new","name":"server.tasks.new","action":"Pterodactyl\Http\Controllers\Server\TaskController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\TaskController@store"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/toggle\/{id}","name":"server.tasks.toggle","action":"Pterodactyl\Http\Controllers\Server\TaskController@toggle"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/tasks\/delete\/{id}","name":"server.tasks.delete","action":"Pterodactyl\Http\Controllers\Server\TaskController@delete"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/set-primary","name":"server.ajax.set-primary","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSetPrimary"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/settings\/reset-database-password","name":"server.ajax.reset-database-password","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postResetDatabasePassword"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@listServices"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"daemon.pull","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], + rootUrl: 'http://pterodactyl.local', + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{identifier}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/api","name":"admin.api.index","action":"Pterodactyl\Http\Controllers\Admin\ApplicationApiController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/api\/new","name":"admin.api.new","action":"Pterodactyl\Http\Controllers\Admin\ApplicationApiController@create"},{"host":null,"methods":["POST"],"uri":"admin\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ApplicationApiController@store"},{"host":null,"methods":["DELETE"],"uri":"admin\/api\/revoke\/{identifier}","name":"admin.api.delete","action":"Pterodactyl\Http\Controllers\Admin\ApplicationApiController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/mail","name":"admin.settings.mail","action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/advanced","name":"admin.settings.advanced","action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@index"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/mail","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/advanced","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@loginUsingTotp"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@toggle"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/trigger","name":"server.schedules.trigger","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@trigger"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users","name":"api.admin.user.list","action":"Pterodactyl\Http\Controllers\Api\Application\Users\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.view","action":"Pterodactyl\Http\Controllers\Api\Application\Users\UserController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/users","name":"api.admin.user.store","action":"Pterodactyl\Http\Controllers\Api\Application\Users\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.update","action":"Pterodactyl\Http\Controllers\Api\Application\Users\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.delete","action":"Pterodactyl\Http\Controllers\Api\Application\Users\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes","name":"api.admin.node.list","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\NodeController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.view","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\NodeController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/nodes","name":"api.admin.node.store","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\NodeController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.update","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\NodeController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.delete","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\NodeController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{node}\/allocations","name":"api.admin.node.allocations.list","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\AllocationController@index"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{node}\/allocations\/{allocation}","name":"api.admin.node.allocations.delete","action":"Pterodactyl\Http\Controllers\Api\Application\Nodes\AllocationController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations","name":"api.admin.location.list","action":"Pterodactyl\Http\Controllers\Api\Application\Locations\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.view","action":"Pterodactyl\Http\Controllers\Api\Application\Locations\LocationController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/locations","name":"api.admin.location.store","action":"Pterodactyl\Http\Controllers\Api\Application\Locations\LocationController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.update","action":"Pterodactyl\Http\Controllers\Api\Application\Locations\LocationController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.delete","action":"Pterodactyl\Http\Controllers\Api\Application\Locations\LocationController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\Api\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\Api\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\Api\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/scripts\/{uuid}","name":"api.remote.scripts","action":"Pterodactyl\Http\Controllers\Api\Remote\EggInstallController@index"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\Api\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"}], prefix: '', route : function (name, parameters, route) { diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index b35c6743c..58003a338 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -23,20 +23,77 @@ @import 'checkbox.css'; .login-page { - height: auto; + background: #10529f; } -.login-box, .register-box { - width: 40%; - max-width: 500px; - margin: 7% auto; +#login-position-elements { + margin: 25% auto; } -@media (max-width:768px) { - .login-box, .register-box { - width: 90%; - margin-top: 20px - } +.login-logo { + color: #fff; + font-weight: 400; +} + +.login-copyright { + color: rgba(255, 255, 255, 0.3); +} + +.login-copyright > a { + color: rgba(255, 255, 255, 0.6); +} + +.particles-js-canvas-el { + position: absolute; + width: 100%; + height: 100%; + top: 0; + z-index: -1; +} + +.pterodactyl-login-box { + background: rgba(0, 0, 0, 0.25); + border-radius: 3px; + padding: 20px; +} + +.pterodactyl-login-input > input { + background: rgba(0, 0, 0, 0.4); + border: 1px solid #000; + border-radius: 2px; + color: #fff; +} + +.pterodactyl-login-input > .form-control-feedback { + color: #fff; +} + +.pterodactyl-login-button--main { + background: rgba(0, 0, 0, 0.4); + border: 1px solid #000; + border-radius: 2px; + color: #fff; +} + +.pterodactyl-login-button--main:hover { + background: rgba(0, 0, 0, 0.7); + border: 1px solid #000; + border-radius: 2px; + color: #fff; +} + +.pterodactyl-login-button--left { + background: rgba(255, 255, 255, 0.4); + border: 1px solid rgba(255, 255, 255, 0.6); + border-radius: 2px; + color: #fff; +} + +.pterodactyl-login-button--left:hover { + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.8); + border-radius: 2px; + color: #fff; } .weight-100 { @@ -57,7 +114,23 @@ } code { + background-color: #eef1f6; + color: #596981; + border-radius: 2px; + padding-left: 4px; + padding-right: 4px; + line-height: 1.4; font-size: 85%; + border: 1px solid rgba(0, 0, 0, .1); + display: inline-block; +} + +p { + line-height: 1.6 !important; +} + +p.small { + margin-top: 3px !important; } .control-sidebar-dark .control-sidebar-menu > li > a.active { @@ -166,14 +239,50 @@ span[aria-labelledby="select2-pUserId-container"] { padding-left: 2px !important; } +.box { + box-shadow: 0 0 0 1px rgba(89, 105, 128, .1), 0 1px 3px 0 rgba(89, 105, 128, .1), 0 1px 2px 0 rgba(0, 0, 0, .05) !important; +} + +.alert-danger { + color: #ffffff !important; + background: #d64242 !important; + border: 1px solid #841d1d; +} + +.alert-info { + color: #ffffff !important; + background: #408fec !important; + border: 1px solid #1055a5; +} + +.alert-success { + color: #ffffff !important; + background: #51b060 !important; + border: 1px solid #2b5f33; +} + +.alert-warning { + color: #ffffff !important; + background: #fa9636 !important; + border: 1px solid #b45b05; +} + .callout-slim a { color: #555 !important; } +.bg-purple { + background-color: #79589f !important; +} + +.label-default { + background-color: #eef1f6 !important; +} + .callout.callout-info.callout-slim { - border: 1px solid #0097bc !important; - border-left: 5px solid #0097bc !important; - border-right: 5px solid #0097bc !important; + border: 1px solid #1055a5 !important; + border-left: 5px solid #1055a5 !important; + border-right: 5px solid #1055a5 !important; color: #777 !important; background: transparent !important; } @@ -257,6 +366,10 @@ span[aria-labelledby="select2-pUserId-container"] { position: relative; } +.no-pad-bottom { + padding-bottom: 0 !important; +} + .no-margin-bottom { margin-bottom: 0 !important; } @@ -265,6 +378,10 @@ span[aria-labelledby="select2-pUserId-container"] { line-height: 1.5; } +.btn.active, .btn.active.focus { + background-color: #408fec; +} + .strong { font-weight: bold !important; } @@ -282,6 +399,7 @@ tr:hover + tr.server-description { position: absolute; bottom: 5px; right: 10px; + color: white; } input.form-autocomplete-stop[readonly] { @@ -295,3 +413,54 @@ input.form-autocomplete-stop[readonly] { background: white; box-shadow: none !important; } + +.dropdown-massactions { + min-width: 80px; +} + +.select-all-files { + position: relative; + bottom: 1px; + margin-right: 7px !important; +} + +.select-file { + position: relative; + bottom: 1px; + margin-right: 2px !important; +} + +.select-folder { + position: relative; + bottom: 1px; + margin-right: 5px !important; +} + +label.control-label > span { + font-size: 80%; + font-weight: 400; + font-style: italic; + color: #dd4b39; +} + +label.control-label > span.field-required:before { + content: "required"; + color: #dd4b39; +} + +label.control-label > span.field-optional:before { + content: "optional"; + color: #bbbbbb; +} + +.pagination > li > a, .pagination > li > span { + padding: 3px 10px !important; +} + +body.sidebar-collapse .main-header .logo { + overflow: hidden; + text-indent: 100%; + background-image: url('/favicons/favicon-32x32.png'); + background-repeat: no-repeat; + background-position: center; +} diff --git a/public/themes/pterodactyl/css/terminal.css b/public/themes/pterodactyl/css/terminal.css index 222639a1d..ecdd10ffc 100644 --- a/public/themes/pterodactyl/css/terminal.css +++ b/public/themes/pterodactyl/css/terminal.css @@ -61,6 +61,7 @@ opacity: .5; font-size: 16px; cursor: pointer; + z-index: 10; } .terminal-notify:hover { diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index f3de55bee..97f05487b 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -18,21 +18,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. $(document).ready(function() { - $('#pServiceId').select2({ - placeholder: 'Select a Service', + $('#pNestId').select2({ + placeholder: 'Select a Nest', }).change(); - $('#pOptionId').select2({ - placeholder: 'Select a Service Option', + $('#pEggId').select2({ + placeholder: 'Select a Nest Egg', }); $('#pPackId').select2({ placeholder: 'Select a Service Pack', }); - $('#pLocationId').select2({ - placeholder: 'Select a Location', - }).change(); $('#pNodeId').select2({ placeholder: 'Select a Node', - }); + }).change(); $('#pAllocation').select2({ placeholder: 'Select a Default Allocation', }); @@ -82,14 +79,6 @@ $(document).ready(function() { }); }); -function hideLoader() { - $('#allocationLoader').hide(); -} - -function showLoader() { - $('#allocationLoader').show(); -} - var lastActiveBox = null; $(document).on('click', function (event) { if (lastActiveBox !== null) { @@ -100,32 +89,9 @@ $(document).on('click', function (event) { lastActiveBox.addClass('box-primary'); }); -var currentLocation = null; -var curentNode = null; -var NodeData = []; - -$('#pLocationId').on('change', function (event) { - showLoader(); - currentLocation = $(this).val(); - currentNode = null; - - $.ajax({ - method: 'POST', - url: Router.route('admin.servers.new.nodes'), - headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, - data: { location: currentLocation }, - }).done(function (data) { - NodeData = data; - $('#pNodeId').html('').select2({data: data}).change(); - }).fail(function (jqXHR) { - cosole.error(jqXHR); - currentLocation = null; - }).always(hideLoader); -}); - -$('#pNodeId').on('change', function (event) { +$('#pNodeId').on('change', function () { currentNode = $(this).val(); - $.each(NodeData, function (i, v) { + $.each(Pterodactyl.nodeData, function (i, v) { if (v.id == currentNode) { $('#pAllocation').html('').select2({ data: v.allocations, @@ -139,9 +105,9 @@ $('#pNodeId').on('change', function (event) { }); }); -$('#pServiceId').on('change', function (event) { - $('#pOptionId').html('').select2({ - data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { +$('#pNestId').on('change', function (event) { + $('#pEggId').html('').select2({ + data: $.map(_.get(Pterodactyl.nests, $(this).val() + '.eggs', []), function (item) { return { id: item.id, text: item.name, @@ -150,9 +116,9 @@ $('#pServiceId').on('change', function (event) { }).change(); }); -$('#pOptionId').on('change', function (event) { - var parentChain = _.get(Pterodactyl.services, $('#pServiceId').val(), null); - var objectChain = _.get(parentChain, 'options.' + $(this).val(), null); +$('#pEggId').on('change', function (event) { + var parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); + var objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); $('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!')); @@ -179,7 +145,7 @@ $('#pOptionId').on('change', function (event) { var dataAppend = ' \
\ \ - \ + \

' + item.description + '
\ Access in Startup: {{' + item.env_variable + '}}
\ Validation Rules: ' + item.rules + '

\ diff --git a/public/themes/pterodactyl/js/admin/node/view-servers.js b/public/themes/pterodactyl/js/admin/node/view-servers.js index 512dbc794..9041379cc 100644 --- a/public/themes/pterodactyl/js/admin/node/view-servers.js +++ b/public/themes/pterodactyl/js/admin/node/view-servers.js @@ -43,7 +43,7 @@ var notifySocketError = false; // Main Socket Object - window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/stats/', { + window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/stats/', { 'query': 'token=' + Pterodactyl.node.daemonSecret, }); diff --git a/public/themes/pterodactyl/js/frontend/2fa-modal.js b/public/themes/pterodactyl/js/frontend/2fa-modal.js index 022ece2ff..d542b377c 100644 --- a/public/themes/pterodactyl/js/frontend/2fa-modal.js +++ b/public/themes/pterodactyl/js/frontend/2fa-modal.js @@ -42,7 +42,6 @@ var TwoFactorModal = (function () { $('#qr_image_insert').attr('src', image.src).slideDown(); }); }); - $('#2fa_secret_insert').html(data.secret); $('#open2fa').modal('show'); }).fail(function (jqXHR) { alert('An error occured while attempting to load the 2FA setup modal. Please try again.'); diff --git a/public/themes/pterodactyl/js/frontend/console.js b/public/themes/pterodactyl/js/frontend/console.js index 96639b156..97bd73854 100644 --- a/public/themes/pterodactyl/js/frontend/console.js +++ b/public/themes/pterodactyl/js/frontend/console.js @@ -46,7 +46,7 @@ $(document).ready(function () { } $terminalInput.focus(); - $('.terminal_input--prompt, #terminal_input, #terminal, #terminalNotify').on('click', function () { + $('.terminal_input--prompt, #terminal_input, #terminalNotify').on('click', function () { $terminalInput.focus(); }); @@ -116,13 +116,15 @@ $(document).ready(function () { }); $terminal.on('scroll', function () { - if ($(this).scrollTop() + $(this).innerHeight() < $(this)[0].scrollHeight) { - $scrollNotify.removeClass('hidden'); - } else { + if (isTerminalScrolledDown()) { $scrollNotify.addClass('hidden'); } }); +function isTerminalScrolledDown() { + return $terminal.scrollTop() + $terminal.innerHeight() + 50 > $terminal[0].scrollHeight; +} + window.scrollToBottom = function () { $terminal.scrollTop($terminal[0].scrollHeight); }; @@ -148,16 +150,20 @@ function pushToTerminal(string) { } if (TerminalQueue.length > 0) { + var scrolledDown = isTerminalScrolledDown(); + for (var i = 0; i < CONSOLE_PUSH_COUNT && TerminalQueue.length > 0; i++) { pushToTerminal(TerminalQueue[0]); - if (! $scrollNotify.is(':visible')) { - window.scrollToBottom(); - } - window.ConsoleElements++; TerminalQueue.shift(); } + + if (scrolledDown) { + window.scrollToBottom(); + } else if ($scrollNotify.hasClass('hidden')) { + $scrollNotify.removeClass('hidden'); + } var removeElements = window.ConsoleElements - CONSOLE_OUTPUT_LIMIT; if (removeElements > 0) { @@ -192,14 +198,16 @@ function pushToTerminal(string) { $('#terminal').html(''); data.split(/\n/g).forEach(function (item) { pushToTerminal(item); - window.scrollToBottom(); }); + window.scrollToBottom(); }); Socket.on('console', function (data) { - data.line.split(/\n/g).forEach(function (item) { - TerminalQueue.push(item); - }); + if(data.line) { + data.line.split(/\n/g).forEach(function (item) { + TerminalQueue.push(item); + }); + } }); })(); diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js index d9a96708d..9713f6587 100644 --- a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js +++ b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js @@ -1,5 +1,5 @@ -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+delName+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'DELETE',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/server/file/f/'+delPath+delName,headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid}}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete this file. Please try again.'})})})}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}}]);return ActionsClass}(); +'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+delName+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';$.each(selectedItems,function(key,value){formattedItems+=''+value+', '});formattedItems=formattedItems.slice(0,-2);swal({type:'warning',title:'',text:'Are you sure you want to delete:'+formattedItems+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}}]);return ActionsClass}(); 'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i New File
  • New Folder
  • '}if(Pterodactyl.permissions.downloadFiles||Pterodactyl.permissions.deleteFiles){buildMenu+='
  • '}if(Pterodactyl.permissions.downloadFiles){buildMenu+=''}if(Pterodactyl.permissions.deleteFiles){buildMenu+='
  • Delete
  • '}buildMenu+='';return buildMenu}},{key:'rightClick',value:function rightClick(){var _this=this;$('[data-action="toggleMenu"]').on('mousedown',function(event){event.preventDefault();if($(document).find('#fileOptionMenu').is(':visible')){$('body').trigger('click');return}_this.showMenu(event)});$('#file_listing > tbody td').on('contextmenu',function(event){_this.showMenu(event)})}},{key:'showMenu',value:function showMenu(event){var _this2=this;var parent=$(event.target).closest('tr');var menu=$(this.makeMenu(parent));if(parent.data('type')==='disabled')return;event.preventDefault();$(menu).appendTo('body');$(menu).data('invokedOn',$(event.target)).show().css({position:'absolute',left:event.pageX-150,top:event.pageY});this.activeLine=parent;this.activeLine.addClass('active');var Actions=new ActionsClass(parent,menu);if(Pterodactyl.permissions.moveFiles){$(menu).find('li[data-action="move"]').unbind().on('click',function(e){e.preventDefault();Actions.move()});$(menu).find('li[data-action="rename"]').unbind().on('click',function(e){e.preventDefault();Actions.rename()})}if(Pterodactyl.permissions.copyFiles){$(menu).find('li[data-action="copy"]').unbind().on('click',function(e){e.preventDefault();Actions.copy()})}if(Pterodactyl.permissions.compressFiles){if(parent.data('type')==='folder'){$(menu).find('li[data-action="compress"]').removeClass('hidden')}$(menu).find('li[data-action="compress"]').unbind().on('click',function(e){e.preventDefault();Actions.compress()})}if(Pterodactyl.permissions.decompressFiles){if(_.without(['application/zip','application/gzip','application/x-gzip'],parent.data('mime')).length<3){$(menu).find('li[data-action="decompress"]').removeClass('hidden')}$(menu).find('li[data-action="decompress"]').unbind().on('click',function(e){e.preventDefault();Actions.decompress()})}if(Pterodactyl.permissions.createFiles){$(menu).find('li[data-action="folder"]').unbind().on('click',function(e){e.preventDefault();Actions.folder()})}if(Pterodactyl.permissions.downloadFiles){if(parent.data('type')==='file'){$(menu).find('li[data-action="download"]').removeClass('hidden')}$(menu).find('li[data-action="download"]').unbind().on('click',function(e){e.preventDefault();Actions.download()})}if(Pterodactyl.permissions.deleteFiles){$(menu).find('li[data-action="delete"]').unbind().on('click',function(e){e.preventDefault();Actions.delete()})}$(window).unbind().on('click',function(event){if($(event.target).is('.disable-menu-hide')){event.preventDefault();return}$(menu).unbind().remove();if(!_.isNull(_this2.activeLine))_this2.activeLine.removeClass('active')})}},{key:'directoryClick',value:function directoryClick(){$('a[data-action="directory-view"]').on('click',function(event){event.preventDefault();var path=$(this).parent().data('path')||'';var name=$(this).parent().data('name')||'';window.location.hash=encodeURIComponent(path+name);Files.list()})}}]);return ContextMenuClass}();window.ContextMenu=new ContextMenuClass; -'use strict';var _typeof=typeof Symbol==='function'&&typeof Symbol.iterator==='symbol'?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype?'symbol':typeof obj};var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ActionsClass {\n constructor(element, menu) {\n this.element = element;\n this.menu = menu;\n }\n\n destroy() {\n this.element = undefined;\n }\n\n folder(path) {\n let inputValue\n if (path) {\n inputValue = path\n } else {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.data('name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n if ($(this.element).data('type') === 'file') {\n inputValue = currentPath;\n } else {\n inputValue = `${currentPath}${currentName}/`;\n }\n }\n\n swal({\n type: 'input',\n title: 'Create Folder',\n text: 'Please enter the path and folder name below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: inputValue\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/folder`,\n timeout: 10000,\n data: JSON.stringify({\n path: val,\n }),\n }).done(data => {\n swal.close();\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n move() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Move File',\n text: 'Please enter the new path for the file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/move`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal.close();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n\n }\n\n rename() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentLink = nameBlock.find('a');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const attachEditor = `\n \n \n `;\n\n nameBlock.html(attachEditor);\n const inputField = nameBlock.find('input');\n const inputLoader = nameBlock.find('.input-loader');\n\n inputField.focus();\n inputField.on('blur keydown', e => {\n // Save Field\n if (\n (e.type === 'keydown' && e.which === 27)\n || e.type === 'blur'\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\n ) {\n if (!_.isEmpty(currentLink)) {\n nameBlock.html(currentLink);\n } else {\n nameBlock.html(currentName);\n }\n inputField.remove();\n ContextMenu.unbind().run();\n return;\n }\n\n if (e.type === 'keydown' && e.which !== 13) return;\n\n inputLoader.show();\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/rename`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${currentPath}${inputField.val()}`,\n }),\n }).done(data => {\n nameBlock.attr('data-name', inputField.val());\n if (!_.isEmpty(currentLink)) {\n let newLink = currentLink.attr('href');\n if (nameBlock.parent().data('type') !== 'folder') {\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\n }\n currentLink.attr('href', newLink);\n nameBlock.html(\n currentLink.html(inputField.val())\n );\n } else {\n nameBlock.html(inputField.val());\n }\n inputField.remove();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n nameBlock.addClass('has-error').delay(2000).queue(() => {\n nameBlock.removeClass('has-error').dequeue();\n });\n inputField.popover({\n animation: true,\n placement: 'top',\n content: error,\n title: 'Save Error'\n }).popover('show');\n }).always(() => {\n inputLoader.remove();\n ContextMenu.unbind().run();\n });\n });\n }\n\n copy() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Copy File',\n text: 'Please enter the new path for the copied file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/copy`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n swal({\n type: 'success',\n title: '',\n text: 'File successfully copied.'\n });\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n download() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\n const filePath = decodeURIComponent(nameBlock.data('path'));\n\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\n }\n\n delete() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const delPath = decodeURIComponent(nameBlock.data('path'));\n const delName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete ' + delName + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'DELETE',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/f/${delPath}${delName}`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n }\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal({\n type: 'success',\n title: 'File Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete this file. Please try again.',\n });\n });\n });\n }\n\n decompress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n title: ' Decompressing...',\n text: 'This might take a few seconds to complete.',\n html: true,\n allowOutsideClick: false,\n allowEscapeKey: false,\n showConfirmButton: false,\n });\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/decompress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`\n })\n }).done(data => {\n swal.close();\n Files.list(compPath);\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n\n compress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/compress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`,\n to: compPath.toString()\n })\n }).done(data => {\n Files.list(compPath, err => {\n if (err) return;\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\n fileListing.removeClass('success pulsate').dequeue();\n });\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n}\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ContextMenuClass {\n constructor() {\n this.activeLine = null;\n }\n\n run() {\n this.directoryClick();\n this.rightClick();\n }\n\n makeMenu(parent) {\n $(document).find('#fileOptionMenu').remove();\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n\n let newFilePath = $('#file_listing').data('current-dir');\n if (parent.data('type') === 'folder') {\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n newFilePath = `${currentPath}${currentName}`;\n }\n\n let buildMenu = '
      ';\n\n if (Pterodactyl.permissions.moveFiles) {\n buildMenu += '
    • Rename
    • \\\n
    • Move
    • ';\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n buildMenu += '
    • Copy
    • ';\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n buildMenu += '
    • Compress
    • ';\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n buildMenu += '
    • Decompress
    • ';\n }\n\n if (Pterodactyl.permissions.createFiles) {\n buildMenu += '
    • \\\n
    • New File
    • \\\n
    • New Folder
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n buildMenu += '
    • Download
    • ';\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • Delete
    • ';\n }\n\n buildMenu += '
    ';\n return buildMenu;\n }\n\n rightClick() {\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\n event.preventDefault();\n if ($(document).find('#fileOptionMenu').is(':visible')) {\n $('body').trigger('click');\n return;\n }\n this.showMenu(event);\n });\n $('#file_listing > tbody td').on('contextmenu', event => {\n this.showMenu(event);\n });\n }\n\n showMenu(event) {\n const parent = $(event.target).closest('tr');\n const menu = $(this.makeMenu(parent));\n\n if (parent.data('type') === 'disabled') return;\n event.preventDefault();\n\n $(menu).appendTo('body');\n $(menu).data('invokedOn', $(event.target)).show().css({\n position: 'absolute',\n left: event.pageX - 150,\n top: event.pageY,\n });\n\n this.activeLine = parent;\n this.activeLine.addClass('active');\n\n // Handle Events\n const Actions = new ActionsClass(parent, menu);\n if (Pterodactyl.permissions.moveFiles) {\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.move();\n });\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.rename();\n });\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.copy();\n });\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n if (parent.data('type') === 'folder') {\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.compress();\n });\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.decompress();\n });\n }\n\n if (Pterodactyl.permissions.createFiles) {\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.folder();\n });\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n if (parent.data('type') === 'file') {\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.download();\n });\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.delete();\n });\n }\n\n $(window).unbind().on('click', event => {\n if($(event.target).is('.disable-menu-hide')) {\n event.preventDefault();\n return;\n }\n $(menu).unbind().remove();\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n });\n }\n\n directoryClick() {\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\n event.preventDefault();\n\n const path = $(this).parent().data('path') || '';\n const name = $(this).parent().data('name') || '';\n\n window.location.hash = encodeURIComponent(path + name);\n Files.list();\n });\n }\n}\n\nwindow.ContextMenu = new ContextMenuClass;\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass FileManager {\n constructor() {\n this.list(this.decodeHash());\n }\n\n list(path, next) {\n if (_.isUndefined(path)) {\n path = this.decodeHash();\n }\n\n this.loader(true);\n $.ajax({\n type: 'POST',\n url: Pterodactyl.meta.directoryList,\n headers: {\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\n },\n data: {\n directory: path,\n },\n }).done(data => {\n this.loader(false);\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\n ContextMenu.run();\n this.reloadFilesButton();\n this.addFolderButton();\n if (_.isFunction(next)) {\n return next();\n }\n });\n $('#internal_alert').slideUp();\n\n if (typeof Siofu === 'object') {\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\n }\n }).fail(jqXHR => {\n this.loader(false);\n if (_.isFunction(next)) {\n return next(new Error('Failed to load file listing.'));\n }\n swal({\n type: 'error',\n title: 'File Error',\n text: jqXHR.responseText || 'An error occured while attempting to process this request. Please try again.',\n });\n console.error(jqXHR);\n });\n }\n\n loader(show) {\n if (show){\n $('.file-overlay').fadeIn(100);\n } else {\n $('.file-overlay').fadeOut(100);\n }\n }\n\n reloadFilesButton() {\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\n this.list();\n });\n }\n\n addFolderButton() {\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\n })\n }\n\n decodeHash() {\n return decodeURIComponent(window.location.hash.substring(1));\n }\n\n}\n\nwindow.Files = new FileManager;\n"]} \ No newline at end of file +{"version":3,"sources":["src/actions.js","src/contextmenu.js","src/index.js"],"names":[],"mappings":"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","file":"filemanager.min.js","sourcesContent":["\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ActionsClass {\n constructor(element, menu) {\n this.element = element;\n this.menu = menu;\n }\n\n destroy() {\n this.element = undefined;\n }\n\n folder(path) {\n let inputValue\n if (path) {\n inputValue = path\n } else {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.data('name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n if ($(this.element).data('type') === 'file') {\n inputValue = currentPath;\n } else {\n inputValue = `${currentPath}${currentName}/`;\n }\n }\n\n swal({\n type: 'input',\n title: 'Create Folder',\n text: 'Please enter the path and folder name below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: inputValue\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`,\n timeout: 10000,\n data: JSON.stringify({\n path: val,\n }),\n }).done(data => {\n swal.close();\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n move() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Move File',\n text: 'Please enter the new path for the file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal.close();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n\n }\n\n rename() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentLink = nameBlock.find('a');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const attachEditor = `\n \n \n `;\n\n nameBlock.html(attachEditor);\n const inputField = nameBlock.find('input');\n const inputLoader = nameBlock.find('.input-loader');\n\n inputField.focus();\n inputField.on('blur keydown', e => {\n // Save Field\n if (\n (e.type === 'keydown' && e.which === 27)\n || e.type === 'blur'\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\n ) {\n if (!_.isEmpty(currentLink)) {\n nameBlock.html(currentLink);\n } else {\n nameBlock.html(currentName);\n }\n inputField.remove();\n ContextMenu.unbind().run();\n return;\n }\n\n if (e.type === 'keydown' && e.which !== 13) return;\n\n inputLoader.show();\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${currentPath}${inputField.val()}`,\n }),\n }).done(data => {\n nameBlock.attr('data-name', inputField.val());\n if (!_.isEmpty(currentLink)) {\n let newLink = currentLink.attr('href');\n if (nameBlock.parent().data('type') !== 'folder') {\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\n }\n currentLink.attr('href', newLink);\n nameBlock.html(\n currentLink.html(inputField.val())\n );\n } else {\n nameBlock.html(inputField.val());\n }\n inputField.remove();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n nameBlock.addClass('has-error').delay(2000).queue(() => {\n nameBlock.removeClass('has-error').dequeue();\n });\n inputField.popover({\n animation: true,\n placement: 'top',\n content: error,\n title: 'Save Error'\n }).popover('show');\n }).always(() => {\n inputLoader.remove();\n ContextMenu.unbind().run();\n });\n });\n }\n\n copy() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Copy File',\n text: 'Please enter the new path for the copied file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n swal({\n type: 'success',\n title: '',\n text: 'File successfully copied.'\n });\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n download() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\n const filePath = decodeURIComponent(nameBlock.data('path'));\n\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\n }\n\n delete() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const delPath = decodeURIComponent(nameBlock.data('path'));\n const delName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete ' + delName + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: [`${delPath}${delName}`]\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal({\n type: 'success',\n title: 'File Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete this file. Please try again.',\n });\n });\n });\n }\n\n toggleMassActions() {\n if ($('#file_listing input[type=\"checkbox\"]:checked').length) {\n $('#mass_actions').removeClass('disabled');\n } else {\n $('#mass_actions').addClass('disabled');\n }\n }\n\n toggleHighlight(event) {\n const parent = $(event.currentTarget);\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $(item).prop('checked', false);\n parent.removeClass('warning').delay(200);\n } else {\n $(item).prop('checked', true);\n parent.addClass('warning').delay(200);\n }\n }\n\n highlightAll(event) {\n let parent;\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $('#file_listing input[type=checkbox]').prop('checked', false);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.removeClass('warning').delay(200);\n });\n } else {\n $('#file_listing input[type=checkbox]').prop('checked', true);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.addClass('warning').delay(200);\n });\n }\n }\n\n deleteSelected() {\n let selectedItems = [];\n let selectedItemsElements = [];\n let parent;\n let nameBlock;\n let delLocation;\n\n $('#file_listing input[data-action=\"addSelection\"]:checked').each(function() {\n parent = $(this).closest('tr');\n nameBlock = $(parent).find('td[data-identifier=\"name\"]');\n delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));\n\n selectedItems.push(delLocation);\n selectedItemsElements.push(parent);\n });\n\n if (selectedItems.length != 0)\n {\n let formattedItems = \"\";\n $.each(selectedItems, function(key, value) {\n formattedItems += (\"\" + value + \", \");\n })\n\n formattedItems = formattedItems.slice(0, -2);\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete:' + formattedItems + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: selectedItems\n }),\n }).done(data => {\n $('#file_listing input:checked').each(function() {\n $(this).prop('checked', false);\n });\n\n $.each(selectedItemsElements, function() {\n $(this).addClass('warning').delay(200).fadeOut();\n })\n\n swal({\n type: 'success',\n title: 'Files Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete these files. Please try again.',\n });\n });\n });\n } else {\n swal({\n type: 'warning',\n title: '',\n text: 'Please select files/folders to delete.',\n });\n }\n }\n\n decompress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n title: ' Decompressing...',\n text: 'This might take a few seconds to complete.',\n html: true,\n allowOutsideClick: false,\n allowEscapeKey: false,\n showConfirmButton: false,\n });\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`\n })\n }).done(data => {\n swal.close();\n Files.list(compPath);\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n\n compress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`,\n to: compPath.toString()\n })\n }).done(data => {\n Files.list(compPath, err => {\n if (err) return;\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\n fileListing.removeClass('success pulsate').dequeue();\n });\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n}\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ContextMenuClass {\n constructor() {\n this.activeLine = null;\n }\n\n run() {\n this.directoryClick();\n this.rightClick();\n }\n\n makeMenu(parent) {\n $(document).find('#fileOptionMenu').remove();\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n\n let newFilePath = $('#file_listing').data('current-dir');\n if (parent.data('type') === 'folder') {\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n newFilePath = `${currentPath}${currentName}`;\n }\n\n let buildMenu = '
      ';\n\n if (Pterodactyl.permissions.moveFiles) {\n buildMenu += '
    • Rename
    • \\\n
    • Move
    • ';\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n buildMenu += '
    • Copy
    • ';\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n buildMenu += '
    • Compress
    • ';\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n buildMenu += '
    • Decompress
    • ';\n }\n\n if (Pterodactyl.permissions.createFiles) {\n buildMenu += '
    • \\\n
    • New File
    • \\\n
    • New Folder
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n buildMenu += '
    • Download
    • ';\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • Delete
    • ';\n }\n\n buildMenu += '
    ';\n return buildMenu;\n }\n\n rightClick() {\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\n event.preventDefault();\n if ($(document).find('#fileOptionMenu').is(':visible')) {\n $('body').trigger('click');\n return;\n }\n this.showMenu(event);\n });\n $('#file_listing > tbody td').on('contextmenu', event => {\n this.showMenu(event);\n });\n }\n\n showMenu(event) {\n const parent = $(event.target).closest('tr');\n const menu = $(this.makeMenu(parent));\n\n if (parent.data('type') === 'disabled') return;\n event.preventDefault();\n\n $(menu).appendTo('body');\n $(menu).data('invokedOn', $(event.target)).show().css({\n position: 'absolute',\n left: event.pageX - 150,\n top: event.pageY,\n });\n\n this.activeLine = parent;\n this.activeLine.addClass('active');\n\n // Handle Events\n const Actions = new ActionsClass(parent, menu);\n if (Pterodactyl.permissions.moveFiles) {\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.move();\n });\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.rename();\n });\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.copy();\n });\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n if (parent.data('type') === 'folder') {\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.compress();\n });\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.decompress();\n });\n }\n\n if (Pterodactyl.permissions.createFiles) {\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.folder();\n });\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n if (parent.data('type') === 'file') {\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.download();\n });\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.delete();\n });\n }\n\n $(window).unbind().on('click', event => {\n if($(event.target).is('.disable-menu-hide')) {\n event.preventDefault();\n return;\n }\n $(menu).unbind().remove();\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n });\n }\n\n directoryClick() {\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\n event.preventDefault();\n\n const path = $(this).parent().data('path') || '';\n const name = $(this).parent().data('name') || '';\n\n window.location.hash = encodeURIComponent(path + name);\n Files.list();\n });\n }\n}\n\nwindow.ContextMenu = new ContextMenuClass;\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass FileManager {\n constructor() {\n this.list(this.decodeHash());\n }\n\n list(path, next) {\n if (_.isUndefined(path)) {\n path = this.decodeHash();\n }\n\n this.loader(true);\n $.ajax({\n type: 'POST',\n url: Pterodactyl.meta.directoryList,\n headers: {\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\n },\n data: {\n directory: path,\n },\n }).done(data => {\n this.loader(false);\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\n ContextMenu.run();\n this.reloadFilesButton();\n this.addFolderButton();\n this.selectItem();\n this.selectAll();\n this.selectiveDeletion();\n this.selectRow();\n if (_.isFunction(next)) {\n return next();\n }\n });\n $('#internal_alert').slideUp();\n\n if (typeof Siofu === 'object') {\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\n }\n }).fail(jqXHR => {\n this.loader(false);\n if (_.isFunction(next)) {\n return next(new Error('Failed to load file listing.'));\n }\n\n if ((path !== '' && path !== '/') && jqXHR.status === 404) {\n return this.list('', next);\n }\n\n swal({\n type: 'error',\n title: 'File Error',\n text: jqXHR.responseJSON.errors[0].detail || 'An error occured while attempting to process this request. Please try again.',\n });\n console.error(jqXHR);\n });\n }\n\n loader(show) {\n if (show){\n $('.file-overlay').fadeIn(100);\n } else {\n $('.file-overlay').fadeOut(100);\n }\n }\n\n reloadFilesButton() {\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\n this.list();\n });\n }\n\n selectItem() {\n $('[data-action=\"addSelection\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectAll() {\n $('[data-action=\"selectAll\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectiveDeletion() {\n $('[data-action=\"selective-deletion\"]').on('mousedown', event => {\n new ActionsClass().deleteSelected();\n });\n }\n\n addFolderButton() {\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\n })\n }\n\n selectRow() {\n $('#file_listing tr').on('mousedown', event => {\n if (event.which === 1) {\n if ($(event.target).is('th') || $(event.target).is('input[data-action=\"selectAll\"]')) {\n new ActionsClass().highlightAll(event);\n } else if ($(event.target).is('td') || $(event.target).is('input[data-action=\"addSelection\"]')) {\n new ActionsClass().toggleHighlight(event);\n }\n\n new ActionsClass().toggleMassActions();\n }\n });\n }\n\n decodeHash() {\n return decodeURIComponent(window.location.hash.substring(1));\n }\n\n}\n\nwindow.Files = new FileManager;\n"]} \ No newline at end of file diff --git a/public/themes/pterodactyl/js/frontend/files/src/actions.js b/public/themes/pterodactyl/js/frontend/files/src/actions.js index 2c400fd2a..ccefee340 100644 --- a/public/themes/pterodactyl/js/frontend/files/src/actions.js +++ b/public/themes/pterodactyl/js/frontend/files/src/actions.js @@ -62,7 +62,7 @@ class ActionsClass { 'X-Access-Server': Pterodactyl.server.uuid, }, contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/folder`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`, timeout: 10000, data: JSON.stringify({ path: val, @@ -107,7 +107,7 @@ class ActionsClass { 'X-Access-Server': Pterodactyl.server.uuid, }, contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/move`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`, timeout: 10000, data: JSON.stringify({ from: `${currentPath}${currentName}`, @@ -175,7 +175,7 @@ class ActionsClass { 'X-Access-Server': Pterodactyl.server.uuid, }, contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/rename`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`, timeout: 10000, data: JSON.stringify({ from: `${currentPath}${currentName}`, @@ -240,7 +240,7 @@ class ActionsClass { 'X-Access-Server': Pterodactyl.server.uuid, }, contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/copy`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`, timeout: 10000, data: JSON.stringify({ from: `${currentPath}${currentName}`, @@ -292,12 +292,17 @@ class ActionsClass { showLoaderOnConfirm: true }, () => { $.ajax({ - type: 'DELETE', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/f/${delPath}${delName}`, + type: 'POST', headers: { 'X-Access-Token': Pterodactyl.server.daemonSecret, 'X-Access-Server': Pterodactyl.server.uuid, - } + }, + contentType: 'application/json; charset=utf-8', + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, + timeout: 10000, + data: JSON.stringify({ + items: [`${delPath}${delName}`] + }), }).done(data => { nameBlock.parent().addClass('warning').delay(200).fadeOut(); swal({ @@ -316,6 +321,125 @@ class ActionsClass { }); } + toggleMassActions() { + if ($('#file_listing input[type="checkbox"]:checked').length) { + $('#mass_actions').removeClass('disabled'); + } else { + $('#mass_actions').addClass('disabled'); + } + } + + toggleHighlight(event) { + const parent = $(event.currentTarget); + const item = $(event.currentTarget).find('input'); + + if($(item).is(':checked')) { + $(item).prop('checked', false); + parent.removeClass('warning').delay(200); + } else { + $(item).prop('checked', true); + parent.addClass('warning').delay(200); + } + } + + highlightAll(event) { + let parent; + const item = $(event.currentTarget).find('input'); + + if($(item).is(':checked')) { + $('#file_listing input[type=checkbox]').prop('checked', false); + $('#file_listing input[data-action="addSelection"]').each(function() { + parent = $(this).closest('tr'); + parent.removeClass('warning').delay(200); + }); + } else { + $('#file_listing input[type=checkbox]').prop('checked', true); + $('#file_listing input[data-action="addSelection"]').each(function() { + parent = $(this).closest('tr'); + parent.addClass('warning').delay(200); + }); + } + } + + deleteSelected() { + let selectedItems = []; + let selectedItemsElements = []; + let parent; + let nameBlock; + let delLocation; + + $('#file_listing input[data-action="addSelection"]:checked').each(function() { + parent = $(this).closest('tr'); + nameBlock = $(parent).find('td[data-identifier="name"]'); + delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name')); + + selectedItems.push(delLocation); + selectedItemsElements.push(parent); + }); + + if (selectedItems.length != 0) + { + let formattedItems = ""; + $.each(selectedItems, function(key, value) { + formattedItems += ("" + value + ", "); + }) + + formattedItems = formattedItems.slice(0, -2); + + swal({ + type: 'warning', + title: '', + text: 'Are you sure you want to delete:' + formattedItems + '? There is no reversing this action.', + html: true, + showCancelButton: true, + showConfirmButton: true, + closeOnConfirm: false, + showLoaderOnConfirm: true + }, () => { + $.ajax({ + type: 'POST', + headers: { + 'X-Access-Token': Pterodactyl.server.daemonSecret, + 'X-Access-Server': Pterodactyl.server.uuid, + }, + contentType: 'application/json; charset=utf-8', + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, + timeout: 10000, + data: JSON.stringify({ + items: selectedItems + }), + }).done(data => { + $('#file_listing input:checked').each(function() { + $(this).prop('checked', false); + }); + + $.each(selectedItemsElements, function() { + $(this).addClass('warning').delay(200).fadeOut(); + }) + + swal({ + type: 'success', + title: 'Files Deleted' + }); + }).fail(jqXHR => { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + html: true, + text: 'An error occured while attempting to delete these files. Please try again.', + }); + }); + }); + } else { + swal({ + type: 'warning', + title: '', + text: 'Please select files/folders to delete.', + }); + } + } + decompress() { const nameBlock = $(this.element).find('td[data-identifier="name"]'); const compPath = decodeURIComponent(nameBlock.data('path')); @@ -332,7 +456,7 @@ class ActionsClass { $.ajax({ type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/decompress`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`, headers: { 'X-Access-Token': Pterodactyl.server.daemonSecret, 'X-Access-Server': Pterodactyl.server.uuid, @@ -366,7 +490,7 @@ class ActionsClass { $.ajax({ type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/server/file/compress`, + url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`, headers: { 'X-Access-Token': Pterodactyl.server.daemonSecret, 'X-Access-Server': Pterodactyl.server.uuid, diff --git a/public/themes/pterodactyl/js/frontend/files/src/index.js b/public/themes/pterodactyl/js/frontend/files/src/index.js index 768cc2eed..b407c0f0b 100644 --- a/public/themes/pterodactyl/js/frontend/files/src/index.js +++ b/public/themes/pterodactyl/js/frontend/files/src/index.js @@ -45,6 +45,10 @@ class FileManager { ContextMenu.run(); this.reloadFilesButton(); this.addFolderButton(); + this.selectItem(); + this.selectAll(); + this.selectiveDeletion(); + this.selectRow(); if (_.isFunction(next)) { return next(); } @@ -59,10 +63,15 @@ class FileManager { if (_.isFunction(next)) { return next(new Error('Failed to load file listing.')); } + + if ((path !== '' && path !== '/') && jqXHR.status === 404) { + return this.list('', next); + } + swal({ type: 'error', title: 'File Error', - text: jqXHR.responseText || 'An error occured while attempting to process this request. Please try again.', + text: jqXHR.responseJSON.errors[0].detail || 'An error occured while attempting to process this request. Please try again.', }); console.error(jqXHR); }); @@ -83,12 +92,44 @@ class FileManager { }); } + selectItem() { + $('[data-action="addSelection"]').on('click', event => { + event.preventDefault(); + }); + } + + selectAll() { + $('[data-action="selectAll"]').on('click', event => { + event.preventDefault(); + }); + } + + selectiveDeletion() { + $('[data-action="selective-deletion"]').on('mousedown', event => { + new ActionsClass().deleteSelected(); + }); + } + addFolderButton() { $('[data-action="add-folder"]').unbind().on('click', () => { new ActionsClass().folder($('#file_listing').data('current-dir') || '/'); }) } + selectRow() { + $('#file_listing tr').on('mousedown', event => { + if (event.which === 1) { + if ($(event.target).is('th') || $(event.target).is('input[data-action="selectAll"]')) { + new ActionsClass().highlightAll(event); + } else if ($(event.target).is('td') || $(event.target).is('input[data-action="addSelection"]')) { + new ActionsClass().toggleHighlight(event); + } + + new ActionsClass().toggleMassActions(); + } + }); + } + decodeHash() { return decodeURIComponent(window.location.hash.substring(1)); } diff --git a/public/themes/pterodactyl/js/frontend/files/upload.js b/public/themes/pterodactyl/js/frontend/files/upload.js index 1206b70e9..2bb1dd8c7 100644 --- a/public/themes/pterodactyl/js/frontend/files/upload.js +++ b/public/themes/pterodactyl/js/frontend/files/upload.js @@ -19,7 +19,7 @@ // SOFTWARE. (function initUploader() { var notifyUploadSocketError = false; - uploadSocket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/upload/' + Pterodactyl.server.uuid, { + uploadSocket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/upload/' + Pterodactyl.server.uuid, { 'query': 'token=' + Pterodactyl.server.daemonSecret, }); diff --git a/public/themes/pterodactyl/js/frontend/server.socket.js b/public/themes/pterodactyl/js/frontend/server.socket.js index 7314c7d7a..f43409fd4 100644 --- a/public/themes/pterodactyl/js/frontend/server.socket.js +++ b/public/themes/pterodactyl/js/frontend/server.socket.js @@ -53,10 +53,22 @@ var Server = (function () { var notifySocketError = false; - window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/ws/' + Pterodactyl.server.uuid, { + window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/ws/' + Pterodactyl.server.uuid, { 'query': 'token=' + Pterodactyl.server.daemonSecret, }); + Socket.on('error', function (err) { + if(typeof notifySocketError !== 'object') { + notifySocketError = $.notify({ + message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

    ' + err, + }, { + type: 'danger', + delay: 0, + }); + } + setStatusIcon(999); + }); + Socket.io.on('connect_error', function (err) { if(typeof notifySocketError !== 'object') { notifySocketError = $.notify({ @@ -66,6 +78,7 @@ var Server = (function () { delay: 0, }); } + setStatusIcon(999); }); // Connected to Socket Successfully @@ -100,6 +113,7 @@ var Server = (function () { $('#server_status_icon').html(' Stopping'); break; default: + $('#server_status_icon').html(' Connection Error'); break; } } diff --git a/public/themes/pterodactyl/js/frontend/tasks.js b/public/themes/pterodactyl/js/frontend/tasks.js deleted file mode 100644 index b19e19556..000000000 --- a/public/themes/pterodactyl/js/frontend/tasks.js +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -var Tasks = (function () { - - function initTaskFunctions() { - $('[data-action="delete-task"]').click(function (event) { - var self = $(this); - swal({ - type: 'error', - title: 'Delete Task?', - text: 'Are you sure you want to delete this task? There is no undo.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Delete Task', - confirmButtonColor: '#d9534f', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'DELETE', - url: Router.route('server.tasks.delete', { - server: Pterodactyl.server.uuidShort, - id: self.data('id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Task has been deleted.' - }); - self.parent().parent().slideUp(); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occured while attempting to delete this task.' - }); - }); - }); - }); - $('[data-action="toggle-task"]').click(function (event) { - var self = $(this); - swal({ - type: 'info', - title: 'Toggle Task', - text: 'This will toggle the selected task.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.tasks.toggle', { - server: Pterodactyl.server.uuidShort, - id: self.data('id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Task has been toggled.' - }); - if (data.status !== 1) { - self.parent().parent().addClass('muted muted-hover'); - } else { - self.parent().parent().removeClass('muted muted-hover'); - } - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occured while attempting to toggle this task.' - }); - }); - }); - }); - } - - return { - init: function () { - initTaskFunctions(); - } - } - -})(); - -Tasks.init(); diff --git a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js new file mode 100644 index 000000000..3c344a971 --- /dev/null +++ b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js @@ -0,0 +1,144 @@ +// Copyright (c) 2015 - 2017 Dane Everitt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +$(document).ready(function () { + $('[data-toggle="tooltip"]').tooltip(); + $('[data-action="delete-schedule"]').click(function () { + var self = $(this); + swal({ + type: 'error', + title: 'Delete Schedule?', + text: 'Are you sure you want to delete this schedule? There is no undo.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Delete Schedule', + confirmButtonColor: '#d9534f', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'DELETE', + url: Router.route('server.schedules.view', { + server: Pterodactyl.server.uuidShort, + schedule: self.data('schedule-id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + } + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Schedule has been deleted.' + }); + self.parent().parent().slideUp(); + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to delete this schedule.' + }); + }); + }); + }); + + $('[data-action="trigger-schedule"]').click(function (event) { + event.preventDefault(); + var self = $(this); + swal({ + type: 'info', + title: 'Trigger Schedule', + text: 'This will run the selected schedule now.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Continue', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'POST', + url: Router.route('server.schedules.trigger', { + server: Pterodactyl.server.uuidShort, + schedule: self.data('schedule-id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + }, + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Schedule has been added to the next-run queue.' + }); + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to trigger this schedule.' + }); + }); + }); + }); + + $('[data-action="toggle-schedule"]').click(function (event) { + var self = $(this); + swal({ + type: 'info', + title: 'Toggle Schedule', + text: 'This will toggle the selected schedule.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Continue', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'POST', + url: Router.route('server.schedules.toggle', { + server: Pterodactyl.server.uuidShort, + schedule: self.data('schedule-id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + } + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Schedule has been toggled.' + }); + if (data.status !== 1) { + self.parent().parent().addClass('muted muted-hover'); + } else { + self.parent().parent().removeClass('muted muted-hover'); + } + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to toggle this schedule.' + }); + }); + }); + }); +}); diff --git a/public/themes/pterodactyl/js/frontend/tasks/view-actions.js b/public/themes/pterodactyl/js/frontend/tasks/view-actions.js new file mode 100644 index 000000000..86b7f8561 --- /dev/null +++ b/public/themes/pterodactyl/js/frontend/tasks/view-actions.js @@ -0,0 +1,61 @@ +// Copyright (c) 2015 - 2017 Dane Everitt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +$(document).ready(function () { + function setupSelect2() { + $('select[name="tasks[time_value][]"]').select2(); + $('select[name="tasks[time_interval][]"]').select2(); + $('select[name="tasks[action][]"]').select2(); + } + + setupSelect2(); + + $('[data-action="update-field"]').on('change', function (event) { + event.preventDefault(); + var updateField = $(this).data('field'); + var selected = $(this).map(function (i, opt) { + return $(opt).val(); + }).toArray(); + if (selected.length === $(this).find('option').length) { + $('input[name=' + updateField + ']').val('*'); + } else { + $('input[name=' + updateField + ']').val(selected.join(',')); + } + }); + + $('button[data-action="add-new-task"]').on('click', function () { + if ($('#containsTaskList').find('.task-list-item').length >= 5) { + swal('Task Limit Reached', 'You may only assign a maximum of 5 tasks to one schedule.'); + return; + } + + var clone = $('div[data-target="task-clone"]').clone(); + clone.insertBefore('#taskAppendBefore').removeAttr('data-target'); + clone.find('select:first').attr('selected'); + clone.find('input').val(''); + clone.find('span.select2-container').remove(); + clone.find('div[data-attribute="remove-task-element"]').addClass('input-group').find('div.input-group-btn').removeClass('hidden'); + clone.find('button[data-action="remove-task"]').on('click', function () { + clone.remove(); + }); + setupSelect2(); + $(this).data('element', clone); + }); +}); diff --git a/public/themes/pterodactyl/vendor/ace/mode-objectivec.js b/public/themes/pterodactyl/vendor/ace/mode-objectivec.js index 08c1cc564..98645e5f6 100644 --- a/public/themes/pterodactyl/vendor/ace/mode-objectivec.js +++ b/public/themes/pterodactyl/vendor/ace/mode-objectivec.js @@ -1 +1 @@ -define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/c_cpp_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o=t.cFunctions="\\b(?:hypot(?:f|l)?|s(?:scanf|ystem|nprintf|ca(?:nf|lb(?:n(?:f|l)?|ln(?:f|l)?))|i(?:n(?:h(?:f|l)?|f|l)?|gn(?:al|bit))|tr(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?)|error|pbrk|ftime|len|rchr|xfrm)|printf|et(?:jmp|vbuf|locale|buf)|qrt(?:f|l)?|w(?:scanf|printf)|rand)|n(?:e(?:arbyint(?:f|l)?|xt(?:toward(?:f|l)?|after(?:f|l)?))|an(?:f|l)?)|c(?:s(?:in(?:h(?:f|l)?|f|l)?|qrt(?:f|l)?)|cos(?:h(?:f)?|f|l)?|imag(?:f|l)?|t(?:ime|an(?:h(?:f|l)?|f|l)?)|o(?:s(?:h(?:f|l)?|f|l)?|nj(?:f|l)?|pysign(?:f|l)?)|p(?:ow(?:f|l)?|roj(?:f|l)?)|e(?:il(?:f|l)?|xp(?:f|l)?)|l(?:o(?:ck|g(?:f|l)?)|earerr)|a(?:sin(?:h(?:f|l)?|f|l)?|cos(?:h(?:f|l)?|f|l)?|tan(?:h(?:f|l)?|f|l)?|lloc|rg(?:f|l)?|bs(?:f|l)?)|real(?:f|l)?|brt(?:f|l)?)|t(?:ime|o(?:upper|lower)|an(?:h(?:f|l)?|f|l)?|runc(?:f|l)?|gamma(?:f|l)?|mp(?:nam|file))|i(?:s(?:space|n(?:ormal|an)|cntrl|inf|digit|u(?:nordered|pper)|p(?:unct|rint)|finite|w(?:space|c(?:ntrl|type)|digit|upper|p(?:unct|rint)|lower|al(?:num|pha)|graph|xdigit|blank)|l(?:ower|ess(?:equal|greater)?)|al(?:num|pha)|gr(?:eater(?:equal)?|aph)|xdigit|blank)|logb(?:f|l)?|max(?:div|abs))|di(?:v|fftime)|_Exit|unget(?:c|wc)|p(?:ow(?:f|l)?|ut(?:s|c(?:har)?|wc(?:har)?)|error|rintf)|e(?:rf(?:c(?:f|l)?|f|l)?|x(?:it|p(?:2(?:f|l)?|f|l|m1(?:f|l)?)?))|v(?:s(?:scanf|nprintf|canf|printf|w(?:scanf|printf))|printf|f(?:scanf|printf|w(?:scanf|printf))|w(?:scanf|printf)|a_(?:start|copy|end|arg))|qsort|f(?:s(?:canf|e(?:tpos|ek))|close|tell|open|dim(?:f|l)?|p(?:classify|ut(?:s|c|w(?:s|c))|rintf)|e(?:holdexcept|set(?:e(?:nv|xceptflag)|round)|clearexcept|testexcept|of|updateenv|r(?:aiseexcept|ror)|get(?:e(?:nv|xceptflag)|round))|flush|w(?:scanf|ide|printf|rite)|loor(?:f|l)?|abs(?:f|l)?|get(?:s|c|pos|w(?:s|c))|re(?:open|e|ad|xp(?:f|l)?)|m(?:in(?:f|l)?|od(?:f|l)?|a(?:f|l|x(?:f|l)?)?))|l(?:d(?:iv|exp(?:f|l)?)|o(?:ngjmp|cal(?:time|econv)|g(?:1(?:p(?:f|l)?|0(?:f|l)?)|2(?:f|l)?|f|l|b(?:f|l)?)?)|abs|l(?:div|abs|r(?:int(?:f|l)?|ound(?:f|l)?))|r(?:int(?:f|l)?|ound(?:f|l)?)|gamma(?:f|l)?)|w(?:scanf|c(?:s(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?|mbs)|pbrk|ftime|len|r(?:chr|tombs)|xfrm)|to(?:b|mb)|rtomb)|printf|mem(?:set|c(?:hr|py|mp)|move))|a(?:s(?:sert|ctime|in(?:h(?:f|l)?|f|l)?)|cos(?:h(?:f|l)?|f|l)?|t(?:o(?:i|f|l(?:l)?)|exit|an(?:h(?:f|l)?|2(?:f|l)?|f|l)?)|b(?:s|ort))|g(?:et(?:s|c(?:har)?|env|wc(?:har)?)|mtime)|r(?:int(?:f|l)?|ound(?:f|l)?|e(?:name|alloc|wind|m(?:ove|quo(?:f|l)?|ainder(?:f|l)?))|a(?:nd|ise))|b(?:search|towc)|m(?:odf(?:f|l)?|em(?:set|c(?:hr|py|mp)|move)|ktime|alloc|b(?:s(?:init|towcs|rtowcs)|towc|len|r(?:towc|len))))\\b",u=function(){var e="break|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while|catch|operator|try|throw|using",t="asm|__asm__|auto|bool|_Bool|char|_Complex|double|enum|float|_Imaginary|int|long|short|signed|struct|typedef|union|unsigned|void|class|wchar_t|template|char16_t|char32_t",n="const|extern|register|restrict|static|volatile|inline|private|protected|public|friend|explicit|virtual|export|mutable|typename|constexpr|new|delete|alignas|alignof|decltype|noexcept|thread_local",r="and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eqconst_cast|dynamic_cast|reinterpret_cast|static_cast|sizeof|namespace",s="NULL|true|false|TRUE|FALSE|nullptr",u=this.$keywords=this.createKeywordMapper({"keyword.control":e,"storage.type":t,"storage.modifier":n,"keyword.operator":r,"variable.language":"this","constant.language":s},"identifier"),a="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b",f=/\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}U[a-fA-F\d]{8}|.)/.source;this.$rules={start:[{token:"comment",regex:"//$",next:"start"},{token:"comment",regex:"//",next:"singleLineComment"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:"string",regex:"'(?:"+f+"|.)?'"},{token:"string.start",regex:'"',stateName:"qqstring",next:[{token:"string",regex:/\\\s*$/,next:"qqstring"},{token:"constant.language.escape",regex:f},{token:"constant.language.escape",regex:/%[^'"\\]/},{token:"string.end",regex:'"|$',next:"start"},{defaultToken:"string"}]},{token:"string.start",regex:'R"\\(',stateName:"rawString",next:[{token:"string.end",regex:'\\)"',next:"start"},{defaultToken:"string"}]},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"keyword",regex:"#\\s*(?:include|import|pragma|line|define|undef)\\b",next:"directive"},{token:"keyword",regex:"#\\s*(?:endif|if|ifdef|else|elif|ifndef)\\b"},{token:"support.function.C99.c",regex:o},{token:u,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*"},{token:"keyword.operator",regex:/--|\+\+|<<=|>>=|>>>=|<>|&&|\|\||\?:|[*%\/+\-&\^|~!<>=]=?/},{token:"punctuation.operator",regex:"\\?|\\:|\\,|\\;|\\."},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],singleLineComment:[{token:"comment",regex:/\\$/,next:"singleLineComment"},{token:"comment",regex:/$/,next:"start"},{defaultToken:"comment"}],directive:[{token:"constant.other.multiline",regex:/\\/},{token:"constant.other.multiline",regex:/.*\\/},{token:"constant.other",regex:"\\s*<.+?>",next:"start"},{token:"constant.other",regex:'\\s*["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',next:"start"},{token:"constant.other",regex:"\\s*['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",next:"start"},{token:"constant.other",regex:/[^\\\/]+/,next:"start"}]},this.embedRules(i,"doc-",[i.getEndRule("start")]),this.normalizeRules()};r.inherits(u,s),t.c_cppHighlightRules=u}),define("ace/mode/objectivec_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/c_cpp_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./c_cpp_highlight_rules"),o=s.c_cppHighlightRules,u=function(){var e="\\\\(?:[abefnrtv'\"?\\\\]|[0-3]\\d{1,2}|[4-7]\\d?|222|x[a-zA-Z0-9]+)",t=[{regex:"\\b_cmd\\b",token:"variable.other.selector.objc"},{regex:"\\b(?:self|super)\\b",token:"variable.language.objc"}],n=new o,r=n.getRules();this.$rules={start:[{token:"comment",regex:"\\/\\/.*$"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:["storage.type.objc","punctuation.definition.storage.type.objc","entity.name.type.objc","text","entity.other.inherited-class.objc"],regex:"(@)(interface|protocol)(?!.+;)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*)([A-Za-z]+)"},{token:["storage.type.objc"],regex:"(@end)"},{token:["storage.type.objc","entity.name.type.objc","entity.other.inherited-class.objc"],regex:"(@implementation)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*?::\\s*(?:[A-Za-z][A-Za-z0-9]*))?"},{token:"string.begin.objc",regex:'@"',next:"constant_NSString"},{token:"storage.type.objc",regex:"\\bid\\s*<",next:"protocol_list"},{token:"keyword.control.macro.objc",regex:"\\bNS_DURING|NS_HANDLER|NS_ENDHANDLER\\b"},{token:["punctuation.definition.keyword.objc","keyword.control.exception.objc"],regex:"(@)(try|catch|finally|throw)\\b"},{token:["punctuation.definition.keyword.objc","keyword.other.objc"],regex:"(@)(defs|encode)\\b"},{token:["storage.type.id.objc","text"],regex:"(\\bid\\b)(\\s|\\n)?"},{token:"storage.type.objc",regex:"\\bIBOutlet|IBAction|BOOL|SEL|id|unichar|IMP|Class\\b"},{token:["punctuation.definition.storage.type.objc","storage.type.objc"],regex:"(@)(class|protocol)\\b"},{token:["punctuation.definition.storage.type.objc","punctuation"],regex:"(@selector)(\\s*\\()",next:"selectors"},{token:["punctuation.definition.storage.modifier.objc","storage.modifier.objc"],regex:"(@)(synchronized|public|private|protected|package)\\b"},{token:"constant.language.objc",regex:"\\bYES|NO|Nil|nil\\b"},{token:"support.variable.foundation",regex:"\\bNSApp\\b"},{token:["support.function.cocoa.leopard"],regex:"(?:\\b)(NS(?:Rect(?:ToCGRect|FromCGRect)|MakeCollectable|S(?:tringFromProtocol|ize(?:ToCGSize|FromCGSize))|Draw(?:NinePartImage|ThreePartImage)|P(?:oint(?:ToCGPoint|FromCGPoint)|rotocolFromString)|EventMaskFromType|Value))(?:\\b)"},{token:["support.function.cocoa"],regex:"(?:\\b)(NS(?:R(?:ound(?:DownToMultipleOfPageSize|UpToMultipleOfPageSize)|un(?:CriticalAlertPanel(?:RelativeToWindow)?|InformationalAlertPanel(?:RelativeToWindow)?|AlertPanel(?:RelativeToWindow)?)|e(?:set(?:MapTable|HashTable)|c(?:ycleZone|t(?:Clip(?:List)?|F(?:ill(?:UsingOperation|List(?:UsingOperation|With(?:Grays|Colors(?:UsingOperation)?))?)?|romString))|ordAllocationEvent)|turnAddress|leaseAlertPanel|a(?:dPixel|l(?:MemoryAvailable|locateCollectable))|gisterServicesProvider)|angeFromString)|Get(?:SizeAndAlignment|CriticalAlertPanel|InformationalAlertPanel|UncaughtExceptionHandler|FileType(?:s)?|WindowServerMemory|AlertPanel)|M(?:i(?:n(?:X|Y)|d(?:X|Y))|ouseInRect|a(?:p(?:Remove|Get|Member|Insert(?:IfAbsent|KnownAbsent)?)|ke(?:R(?:ect|ange)|Size|Point)|x(?:Range|X|Y)))|B(?:itsPer(?:SampleFromDepth|PixelFromDepth)|e(?:stDepth|ep|gin(?:CriticalAlertSheet|InformationalAlertSheet|AlertSheet)))|S(?:ho(?:uldRetainWithZone|w(?:sServicesMenuItem|AnimationEffect))|tringFrom(?:R(?:ect|ange)|MapTable|S(?:ize|elector)|HashTable|Class|Point)|izeFromString|e(?:t(?:ShowsServicesMenuItem|ZoneName|UncaughtExceptionHandler|FocusRingStyle)|lectorFromString|archPathForDirectoriesInDomains)|wap(?:Big(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|Short|Host(?:ShortTo(?:Big|Little)|IntTo(?:Big|Little)|DoubleTo(?:Big|Little)|FloatTo(?:Big|Little)|Long(?:To(?:Big|Little)|LongTo(?:Big|Little)))|Int|Double|Float|L(?:ittle(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|ong(?:Long)?)))|H(?:ighlightRect|o(?:stByteOrder|meDirectory(?:ForUser)?)|eight|ash(?:Remove|Get|Insert(?:IfAbsent|KnownAbsent)?)|FSType(?:CodeFromFileType|OfFile))|N(?:umberOfColorComponents|ext(?:MapEnumeratorPair|HashEnumeratorItem))|C(?:o(?:n(?:tainsRect|vert(?:GlyphsToPackedGlyphs|Swapped(?:DoubleToHost|FloatToHost)|Host(?:DoubleToSwapped|FloatToSwapped)))|unt(?:MapTable|HashTable|Frames|Windows(?:ForContext)?)|py(?:M(?:emoryPages|apTableWithZone)|Bits|HashTableWithZone|Object)|lorSpaceFromDepth|mpare(?:MapTables|HashTables))|lassFromString|reate(?:MapTable(?:WithZone)?|HashTable(?:WithZone)?|Zone|File(?:namePboardType|ContentsPboardType)))|TemporaryDirectory|I(?:s(?:ControllerMarker|EmptyRect|FreedObject)|n(?:setRect|crementExtraRefCount|te(?:r(?:sect(?:sRect|ionR(?:ect|ange))|faceStyleForKey)|gralRect)))|Zone(?:Realloc|Malloc|Name|Calloc|Fr(?:omPointer|ee))|O(?:penStepRootDirectory|ffsetRect)|D(?:i(?:sableScreenUpdates|videRect)|ottedFrameRect|e(?:c(?:imal(?:Round|Multiply|S(?:tring|ubtract)|Normalize|Co(?:py|mpa(?:ct|re))|IsNotANumber|Divide|Power|Add)|rementExtraRefCountWasZero)|faultMallocZone|allocate(?:MemoryPages|Object))|raw(?:Gr(?:oove|ayBezel)|B(?:itmap|utton)|ColorTiledRects|TiledRects|DarkBezel|W(?:hiteBezel|indowBackground)|LightBezel))|U(?:serName|n(?:ionR(?:ect|ange)|registerServicesProvider)|pdateDynamicServices)|Java(?:Bundle(?:Setup|Cleanup)|Setup(?:VirtualMachine)?|Needs(?:ToLoadClasses|VirtualMachine)|ClassesF(?:orBundle|romPath)|ObjectNamedInPath|ProvidesClasses)|P(?:oint(?:InRect|FromString)|erformService|lanarFromDepth|ageSize)|E(?:n(?:d(?:MapTableEnumeration|HashTableEnumeration)|umerate(?:MapTable|HashTable)|ableScreenUpdates)|qual(?:R(?:ects|anges)|Sizes|Points)|raseRect|xtraRefCount)|F(?:ileTypeForHFSTypeCode|ullUserName|r(?:ee(?:MapTable|HashTable)|ame(?:Rect(?:WithWidth(?:UsingOperation)?)?|Address)))|Wi(?:ndowList(?:ForContext)?|dth)|Lo(?:cationInRange|g(?:v|PageSize)?)|A(?:ccessibility(?:R(?:oleDescription(?:ForUIElement)?|aiseBadArgumentException)|Unignored(?:Children(?:ForOnlyChild)?|Descendant|Ancestor)|PostNotification|ActionDescription)|pplication(?:Main|Load)|vailableWindowDepths|ll(?:MapTable(?:Values|Keys)|HashTableObjects|ocate(?:MemoryPages|Collectable|Object)))))(?:\\b)"},{token:["support.class.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor|G(?:arbageCollector|radient)|MapTable|HashTable|Co(?:ndition|llectionView(?:Item)?)|T(?:oolbarItemGroup|extInputClient|r(?:eeNode|ackingArea))|InvocationOperation|Operation(?:Queue)?|D(?:ictionaryController|ockTile)|P(?:ointer(?:Functions|Array)|athC(?:o(?:ntrol(?:Delegate)?|mponentCell)|ell(?:Delegate)?)|r(?:intPanelAccessorizing|edicateEditor(?:RowTemplate)?))|ViewController|FastEnumeration|Animat(?:ionContext|ablePropertyContainer)))(?:\\b)"},{token:["support.class.cocoa"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.type.cocoa.leopard"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.class.quartz"],regex:"(?:\\b)(C(?:I(?:Sampler|Co(?:ntext|lor)|Image(?:Accumulator)?|PlugIn(?:Registration)?|Vector|Kernel|Filter(?:Generator|Shape)?)|A(?:Renderer|MediaTiming(?:Function)?|BasicAnimation|ScrollLayer|Constraint(?:LayoutManager)?|T(?:iledLayer|extLayer|rans(?:ition|action))|OpenGLLayer|PropertyAnimation|KeyframeAnimation|Layer|A(?:nimation(?:Group)?|ction))))(?:\\b)"},{token:["support.type.quartz"],regex:"(?:\\b)(C(?:G(?:Float|Point|Size|Rect)|IFormat|AConstraintAttribute))(?:\\b)"},{token:["support.type.cocoa"],regex:"(?:\\b)(NS(?:R(?:ect(?:Edge)?|ange)|G(?:lyph(?:Relation|LayoutMode)?|radientType)|M(?:odalSession|a(?:trixMode|p(?:Table|Enumerator)))|B(?:itmapImageFileType|orderType|uttonType|ezelStyle|ackingStoreType|rowserColumnResizingType)|S(?:cr(?:oll(?:er(?:Part|Arrow)|ArrowPosition)|eenAuxiliaryOpaque)|tringEncoding|ize|ocketNativeHandle|election(?:Granularity|Direction|Affinity)|wapped(?:Double|Float)|aveOperationType)|Ha(?:sh(?:Table|Enumerator)|ndler(?:2)?)|C(?:o(?:ntrol(?:Size|Tint)|mp(?:ositingOperation|arisonResult))|ell(?:State|Type|ImagePosition|Attribute))|T(?:hreadPrivate|ypesetterGlyphInfo|i(?:ckMarkPosition|tlePosition|meInterval)|o(?:ol(?:TipTag|bar(?:SizeMode|DisplayMode))|kenStyle)|IFFCompression|ext(?:TabType|Alignment)|ab(?:State|leViewDropOperation|ViewType)|rackingRectTag)|ImageInterpolation|Zone|OpenGL(?:ContextAuxiliary|PixelFormatAuxiliary)|D(?:ocumentChangeType|atePickerElementFlags|ra(?:werState|gOperation))|UsableScrollerParts|P(?:oint|r(?:intingPageOrder|ogressIndicator(?:Style|Th(?:ickness|readInfo))))|EventType|KeyValueObservingOptions|Fo(?:nt(?:SymbolicTraits|TraitMask|Action)|cusRingType)|W(?:indow(?:OrderingMode|Depth)|orkspace(?:IconCreationOptions|LaunchOptions)|ritingDirection)|L(?:ineBreakMode|ayout(?:Status|Direction))|A(?:nimation(?:Progress|Effect)|ppl(?:ication(?:TerminateReply|DelegateReply|PrintReply)|eEventManagerSuspensionID)|ffineTransformStruct|lertStyle)))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:NotFound|Ordered(?:Ascending|Descending|Same)))(?:\\b)"},{token:["support.constant.notification.cocoa.leopard"],regex:"(?:\\b)(NS(?:MenuDidBeginTracking|ViewDidUpdateTrackingAreas)?Notification)(?:\\b)"},{token:["support.constant.notification.cocoa"],regex:"(?:\\b)(NS(?:Menu(?:Did(?:RemoveItem|SendAction|ChangeItem|EndTracking|AddItem)|WillSendAction)|S(?:ystemColorsDidChange|plitView(?:DidResizeSubviews|WillResizeSubviews))|C(?:o(?:nt(?:extHelpModeDid(?:Deactivate|Activate)|rolT(?:intDidChange|extDid(?:BeginEditing|Change|EndEditing)))|lor(?:PanelColorDidChange|ListDidChange)|mboBox(?:Selection(?:IsChanging|DidChange)|Will(?:Dismiss|PopUp)))|lassDescriptionNeededForClass)|T(?:oolbar(?:DidRemoveItem|WillAddItem)|ext(?:Storage(?:DidProcessEditing|WillProcessEditing)|Did(?:BeginEditing|Change|EndEditing)|View(?:DidChange(?:Selection|TypingAttributes)|WillChangeNotifyingTextView))|ableView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)))|ImageRepRegistryDidChange|OutlineView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)|Item(?:Did(?:Collapse|Expand)|Will(?:Collapse|Expand)))|Drawer(?:Did(?:Close|Open)|Will(?:Close|Open))|PopUpButton(?:CellWillPopUp|WillPopUp)|View(?:GlobalFrameDidChange|BoundsDidChange|F(?:ocusDidChange|rameDidChange))|FontSetChanged|W(?:indow(?:Did(?:Resi(?:ze|gn(?:Main|Key))|M(?:iniaturize|ove)|Become(?:Main|Key)|ChangeScreen(?:|Profile)|Deminiaturize|Update|E(?:ndSheet|xpose))|Will(?:M(?:iniaturize|ove)|BeginSheet|Close))|orkspace(?:SessionDid(?:ResignActive|BecomeActive)|Did(?:Mount|TerminateApplication|Unmount|PerformFileOperation|Wake|LaunchApplication)|Will(?:Sleep|Unmount|PowerOff|LaunchApplication)))|A(?:ntialiasThresholdChanged|ppl(?:ication(?:Did(?:ResignActive|BecomeActive|Hide|ChangeScreenParameters|U(?:nhide|pdate)|FinishLaunching)|Will(?:ResignActive|BecomeActive|Hide|Terminate|U(?:nhide|pdate)|FinishLaunching))|eEventManagerWillProcessFirstEvent)))Notification)(?:\\b)"},{token:["support.constant.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor(?:RowType(?:Simple|Compound)|NestingMode(?:Si(?:ngle|mple)|Compound|List))|GradientDraws(?:BeforeStartingLocation|AfterEndingLocation)|M(?:inusSetExpressionType|a(?:chPortDeallocate(?:ReceiveRight|SendRight|None)|pTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality)))|B(?:oxCustom|undleExecutableArchitecture(?:X86|I386|PPC(?:64)?)|etweenPredicateOperatorType|ackgroundStyle(?:Raised|Dark|L(?:ight|owered)))|S(?:tring(?:DrawingTruncatesLastVisibleLine|EncodingConversion(?:ExternalRepresentation|AllowLossy))|ubqueryExpressionType|p(?:e(?:ech(?:SentenceBoundary|ImmediateBoundary|WordBoundary)|llingState(?:GrammarFlag|SpellingFlag))|litViewDividerStyleThi(?:n|ck))|e(?:rvice(?:RequestTimedOutError|M(?:iscellaneousError|alformedServiceDictionaryError)|InvalidPasteboardDataError|ErrorM(?:inimum|aximum)|Application(?:NotFoundError|LaunchFailedError))|gmentStyle(?:Round(?:Rect|ed)|SmallSquare|Capsule|Textured(?:Rounded|Square)|Automatic)))|H(?:UDWindowMask|ashTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality))|N(?:oModeColorPanel|etServiceNoAutoRename)|C(?:hangeRedone|o(?:ntainsPredicateOperatorType|l(?:orRenderingIntent(?:RelativeColorimetric|Saturation|Default|Perceptual|AbsoluteColorimetric)|lectorDisabledOption))|ellHit(?:None|ContentArea|TrackableArea|EditableTextArea))|T(?:imeZoneNameStyle(?:S(?:hort(?:Standard|DaylightSaving)|tandard)|DaylightSaving)|extFieldDatePickerStyle|ableViewSelectionHighlightStyle(?:Regular|SourceList)|racking(?:Mouse(?:Moved|EnteredAndExited)|CursorUpdate|InVisibleRect|EnabledDuringMouseDrag|A(?:ssumeInside|ctive(?:In(?:KeyWindow|ActiveApp)|WhenFirstResponder|Always))))|I(?:n(?:tersectSetExpressionType|dexedColorSpaceModel)|mageScale(?:None|Proportionally(?:Down|UpOrDown)|AxesIndependently))|Ope(?:nGLPFAAllowOfflineRenderers|rationQueue(?:DefaultMaxConcurrentOperationCount|Priority(?:High|Normal|Very(?:High|Low)|Low)))|D(?:iacriticInsensitiveSearch|ownloadsDirectory)|U(?:nionSetExpressionType|TF(?:16(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)|32(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)))|P(?:ointerFunctions(?:Ma(?:chVirtualMemory|llocMemory)|Str(?:ongMemory|uctPersonality)|C(?:StringPersonality|opyIn)|IntegerPersonality|ZeroingWeakMemory|O(?:paque(?:Memory|Personality)|bjectP(?:ointerPersonality|ersonality)))|at(?:hStyle(?:Standard|NavigationBar|PopUp)|ternColorSpaceModel)|rintPanelShows(?:Scaling|Copies|Orientation|P(?:a(?:perSize|ge(?:Range|SetupAccessory))|review)))|Executable(?:RuntimeMismatchError|NotLoadableError|ErrorM(?:inimum|aximum)|L(?:inkError|oadError)|ArchitectureMismatchError)|KeyValueObservingOption(?:Initial|Prior)|F(?:i(?:ndPanelSubstringMatchType(?:StartsWith|Contains|EndsWith|FullWord)|leRead(?:TooLargeError|UnknownStringEncodingError))|orcedOrderingSearch)|Wi(?:ndow(?:BackingLocation(?:MainMemory|Default|VideoMemory)|Sharing(?:Read(?:Only|Write)|None)|CollectionBehavior(?:MoveToActiveSpace|CanJoinAllSpaces|Default))|dthInsensitiveSearch)|AggregateExpressionType))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:R(?:GB(?:ModeColorPanel|ColorSpaceModel)|ight(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey)|ound(?:RectBezelStyle|Bankers|ed(?:BezelStyle|TokenStyle|DisclosureBezelStyle)|Down|Up|Plain|Line(?:CapStyle|JoinStyle))|un(?:StoppedResponse|ContinuesResponse|AbortedResponse)|e(?:s(?:izableWindowMask|et(?:CursorRectsRunLoopOrdering|FunctionKey))|ce(?:ssedBezelStyle|iver(?:sCantHandleCommandScriptError|EvaluationScriptError))|turnTextMovement|doFunctionKey|quiredArgumentsMissingScriptError|l(?:evancyLevelIndicatorStyle|ative(?:Before|After))|gular(?:SquareBezelStyle|ControlSize)|moveTraitFontAction)|a(?:n(?:domSubelement|geDateMode)|tingLevelIndicatorStyle|dio(?:ModeMatrix|Button)))|G(?:IFFileType|lyph(?:Below|Inscribe(?:B(?:elow|ase)|Over(?:strike|Below)|Above)|Layout(?:WithPrevious|A(?:tAPoint|gainstAPoint))|A(?:ttribute(?:BidiLevel|Soft|Inscribe|Elastic)|bove))|r(?:ooveBorder|eaterThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|a(?:y(?:ModeColorPanel|ColorSpaceModel)|dient(?:None|Con(?:cave(?:Strong|Weak)|vex(?:Strong|Weak)))|phiteControlTint)))|XML(?:N(?:o(?:tationDeclarationKind|de(?:CompactEmptyElement|IsCDATA|OptionsNone|Use(?:SingleQuotes|DoubleQuotes)|Pre(?:serve(?:NamespaceOrder|C(?:haracterReferences|DATA)|DTD|Prefixes|E(?:ntities|mptyElements)|Quotes|Whitespace|A(?:ttributeOrder|ll))|ttyPrint)|ExpandEmptyElement))|amespaceKind)|CommentKind|TextKind|InvalidKind|D(?:ocument(?:X(?:MLKind|HTMLKind|Include)|HTMLKind|T(?:idy(?:XML|HTML)|extKind)|IncludeContentTypeDeclaration|Validate|Kind)|TDKind)|P(?:arser(?:GTRequiredError|XMLDeclNot(?:StartedError|FinishedError)|Mi(?:splaced(?:XMLDeclarationError|CDATAEndStringError)|xedContentDeclNot(?:StartedError|FinishedError))|S(?:t(?:andaloneValueError|ringNot(?:StartedError|ClosedError))|paceRequiredError|eparatorRequiredError)|N(?:MTOKENRequiredError|o(?:t(?:ationNot(?:StartedError|FinishedError)|WellBalancedError)|DTDError)|amespaceDeclarationError|AMERequiredError)|C(?:haracterRef(?:In(?:DTDError|PrologError|EpilogError)|AtEOFError)|o(?:nditionalSectionNot(?:StartedError|FinishedError)|mment(?:NotFinishedError|ContainsDoubleHyphenError))|DATANotFinishedError)|TagNameMismatchError|In(?:ternalError|valid(?:HexCharacterRefError|C(?:haracter(?:RefError|InEntityError|Error)|onditionalSectionError)|DecimalCharacterRefError|URIError|Encoding(?:NameError|Error)))|OutOfMemoryError|D(?:ocumentStartError|elegateAbortedParseError|OCTYPEDeclNotFinishedError)|U(?:RI(?:RequiredError|FragmentError)|n(?:declaredEntityError|parsedEntityError|knownEncodingError|finishedTagError))|P(?:CDATARequiredError|ublicIdentifierRequiredError|arsedEntityRef(?:MissingSemiError|NoNameError|In(?:Internal(?:SubsetError|Error)|PrologError|EpilogError)|AtEOFError)|r(?:ocessingInstructionNot(?:StartedError|FinishedError)|ematureDocumentEndError))|E(?:n(?:codingNotSupportedError|tity(?:Ref(?:In(?:DTDError|PrologError|EpilogError)|erence(?:MissingSemiError|WithoutNameError)|LoopError|AtEOFError)|BoundaryError|Not(?:StartedError|FinishedError)|Is(?:ParameterError|ExternalError)|ValueRequiredError))|qualExpectedError|lementContentDeclNot(?:StartedError|FinishedError)|xt(?:ernalS(?:tandaloneEntityError|ubsetNotFinishedError)|raContentError)|mptyDocumentError)|L(?:iteralNot(?:StartedError|FinishedError)|T(?:RequiredError|SlashRequiredError)|essThanSymbolInAttributeError)|Attribute(?:RedefinedError|HasNoValueError|Not(?:StartedError|FinishedError)|ListNot(?:StartedError|FinishedError)))|rocessingInstructionKind)|E(?:ntity(?:GeneralKind|DeclarationKind|UnparsedKind|P(?:ar(?:sedKind|ameterKind)|redefined))|lement(?:Declaration(?:MixedKind|UndefinedKind|E(?:lementKind|mptyKind)|Kind|AnyKind)|Kind))|Attribute(?:N(?:MToken(?:sKind|Kind)|otationKind)|CDATAKind|ID(?:Ref(?:sKind|Kind)|Kind)|DeclarationKind|En(?:tit(?:yKind|iesKind)|umerationKind)|Kind))|M(?:i(?:n(?:XEdge|iaturizableWindowMask|YEdge|uteCalendarUnit)|terLineJoinStyle|ddleSubelement|xedState)|o(?:nthCalendarUnit|deSwitchFunctionKey|use(?:Moved(?:Mask)?|E(?:ntered(?:Mask)?|ventSubtype|xited(?:Mask)?))|veToBezierPathElement|mentary(?:ChangeButton|Push(?:Button|InButton)|Light(?:Button)?))|enuFunctionKey|a(?:c(?:intoshInterfaceStyle|OSRomanStringEncoding)|tchesPredicateOperatorType|ppedRead|x(?:XEdge|YEdge))|ACHOperatingSystem)|B(?:MPFileType|o(?:ttomTabsBezelBorder|ldFontMask|rderlessWindowMask|x(?:Se(?:condary|parator)|OldStyle|Primary))|uttLineCapStyle|e(?:zelBorder|velLineJoinStyle|low(?:Bottom|Top)|gin(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|lueControlTint|ack(?:spaceCharacter|tabTextMovement|ingStore(?:Retained|Buffered|Nonretained)|TabCharacter|wardsSearch|groundTab)|r(?:owser(?:NoColumnResizing|UserColumnResizing|AutoColumnResizing)|eakFunctionKey))|S(?:h(?:ift(?:JISStringEncoding|KeyMask)|ow(?:ControlGlyphs|InvisibleGlyphs)|adowlessSquareBezelStyle)|y(?:s(?:ReqFunctionKey|tem(?:D(?:omainMask|efined(?:Mask)?)|FunctionKey))|mbolStringEncoding)|c(?:a(?:nnedOption|le(?:None|ToFit|Proportionally))|r(?:oll(?:er(?:NoPart|Increment(?:Page|Line|Arrow)|Decrement(?:Page|Line|Arrow)|Knob(?:Slot)?|Arrows(?:M(?:inEnd|axEnd)|None|DefaultSetting))|Wheel(?:Mask)?|LockFunctionKey)|eenChangedEventType))|t(?:opFunctionKey|r(?:ingDrawing(?:OneShot|DisableScreenFontSubstitution|Uses(?:DeviceMetrics|FontLeading|LineFragmentOrigin))|eam(?:Status(?:Reading|NotOpen|Closed|Open(?:ing)?|Error|Writing|AtEnd)|Event(?:Has(?:BytesAvailable|SpaceAvailable)|None|OpenCompleted|E(?:ndEncountered|rrorOccurred)))))|i(?:ngle(?:DateMode|UnderlineStyle)|ze(?:DownFontAction|UpFontAction))|olarisOperatingSystem|unOSOperatingSystem|pecialPageOrder|e(?:condCalendarUnit|lect(?:By(?:Character|Paragraph|Word)|i(?:ng(?:Next|Previous)|onAffinity(?:Downstream|Upstream))|edTab|FunctionKey)|gmentSwitchTracking(?:Momentary|Select(?:One|Any)))|quareLineCapStyle|witchButton|ave(?:ToOperation|Op(?:tions(?:Yes|No|Ask)|eration)|AsOperation)|mall(?:SquareBezelStyle|C(?:ontrolSize|apsFontMask)|IconButtonBezelStyle))|H(?:ighlightModeMatrix|SBModeColorPanel|o(?:ur(?:Minute(?:SecondDatePickerElementFlag|DatePickerElementFlag)|CalendarUnit)|rizontalRuler|meFunctionKey)|TTPCookieAcceptPolicy(?:Never|OnlyFromMainDocumentDomain|Always)|e(?:lp(?:ButtonBezelStyle|KeyMask|FunctionKey)|avierFontAction)|PUXOperatingSystem)|Year(?:MonthDa(?:yDatePickerElementFlag|tePickerElementFlag)|CalendarUnit)|N(?:o(?:n(?:StandardCharacterSetFontMask|ZeroWindingRule|activatingPanelMask|LossyASCIIStringEncoding)|Border|t(?:ification(?:SuspensionBehavior(?:Hold|Coalesce|D(?:eliverImmediately|rop))|NoCoalescing|CoalescingOn(?:Sender|Name)|DeliverImmediately|PostToAllSessions)|PredicateType|EqualToPredicateOperatorType)|S(?:cr(?:iptError|ollerParts)|ubelement|pecifierError)|CellMask|T(?:itle|opLevelContainersSpecifierError|abs(?:BezelBorder|NoBorder|LineBorder))|I(?:nterfaceStyle|mage)|UnderlineStyle|FontChangeAction)|u(?:ll(?:Glyph|CellType)|m(?:eric(?:Search|PadKeyMask)|berFormatter(?:Round(?:Half(?:Down|Up|Even)|Ceiling|Down|Up|Floor)|Behavior(?:10|Default)|S(?:cientificStyle|pellOutStyle)|NoStyle|CurrencyStyle|DecimalStyle|P(?:ercentStyle|ad(?:Before(?:Suffix|Prefix)|After(?:Suffix|Prefix))))))|e(?:t(?:Services(?:BadArgumentError|NotFoundError|C(?:ollisionError|ancelledError)|TimeoutError|InvalidError|UnknownError|ActivityInProgress)|workDomainMask)|wlineCharacter|xt(?:StepInterfaceStyle|FunctionKey))|EXTSTEPStringEncoding|a(?:t(?:iveShortGlyphPacking|uralTextAlignment)|rrowFontMask))|C(?:hange(?:ReadOtherContents|GrayCell(?:Mask)?|BackgroundCell(?:Mask)?|Cleared|Done|Undone|Autosaved)|MYK(?:ModeColorPanel|ColorSpaceModel)|ircular(?:BezelStyle|Slider)|o(?:n(?:stantValueExpressionType|t(?:inuousCapacityLevelIndicatorStyle|entsCellMask|ain(?:sComparison|erSpecifierError)|rol(?:Glyph|KeyMask))|densedFontMask)|lor(?:Panel(?:RGBModeMask|GrayModeMask|HSBModeMask|C(?:MYKModeMask|olorListModeMask|ustomPaletteModeMask|rayonModeMask)|WheelModeMask|AllModesMask)|ListModeColorPanel)|reServiceDirectory|m(?:p(?:osite(?:XOR|Source(?:In|O(?:ut|ver)|Atop)|Highlight|C(?:opy|lear)|Destination(?:In|O(?:ut|ver)|Atop)|Plus(?:Darker|Lighter))|ressedFontMask)|mandKeyMask))|u(?:stom(?:SelectorPredicateOperatorType|PaletteModeColorPanel)|r(?:sor(?:Update(?:Mask)?|PointingDevice)|veToBezierPathElement))|e(?:nterT(?:extAlignment|abStopType)|ll(?:State|H(?:ighlighted|as(?:Image(?:Horizontal|OnLeftOrBottom)|OverlappingImage))|ChangesContents|Is(?:Bordered|InsetButton)|Disabled|Editable|LightsBy(?:Gray|Background|Contents)|AllowsMixedState))|l(?:ipPagination|o(?:s(?:ePathBezierPathElement|ableWindowMask)|ckAndCalendarDatePickerStyle)|ear(?:ControlTint|DisplayFunctionKey|LineFunctionKey))|a(?:seInsensitive(?:Search|PredicateOption)|n(?:notCreateScriptCommandError|cel(?:Button|TextMovement))|chesDirectory|lculation(?:NoError|Overflow|DivideByZero|Underflow|LossOfPrecision)|rriageReturnCharacter)|r(?:itical(?:Request|AlertStyle)|ayonModeColorPanel))|T(?:hick(?:SquareBezelStyle|erSquareBezelStyle)|ypesetter(?:Behavior|HorizontalTabAction|ContainerBreakAction|ZeroAdvancementAction|OriginalBehavior|ParagraphBreakAction|WhitespaceAction|L(?:ineBreakAction|atestBehavior))|i(?:ckMark(?:Right|Below|Left|Above)|tledWindowMask|meZoneDatePickerElementFlag)|o(?:olbarItemVisibilityPriority(?:Standard|High|User|Low)|pTabsBezelBorder|ggleButton)|IFF(?:Compression(?:N(?:one|EXT)|CCITTFAX(?:3|4)|OldJPEG|JPEG|PackBits|LZW)|FileType)|e(?:rminate(?:Now|Cancel|Later)|xt(?:Read(?:InapplicableDocumentTypeError|WriteErrorM(?:inimum|aximum))|Block(?:M(?:i(?:nimum(?:Height|Width)|ddleAlignment)|a(?:rgin|ximum(?:Height|Width)))|B(?:o(?:ttomAlignment|rder)|aselineAlignment)|Height|TopAlignment|P(?:ercentageValueType|adding)|Width|AbsoluteValueType)|StorageEdited(?:Characters|Attributes)|CellType|ured(?:RoundedBezelStyle|BackgroundWindowMask|SquareBezelStyle)|Table(?:FixedLayoutAlgorithm|AutomaticLayoutAlgorithm)|Field(?:RoundedBezel|SquareBezel|AndStepperDatePickerStyle)|WriteInapplicableDocumentTypeError|ListPrependEnclosingMarker))|woByteGlyphPacking|ab(?:Character|TextMovement|le(?:tP(?:oint(?:Mask|EventSubtype)?|roximity(?:Mask|EventSubtype)?)|Column(?:NoResizing|UserResizingMask|AutoresizingMask)|View(?:ReverseSequentialColumnAutoresizingStyle|GridNone|S(?:olid(?:HorizontalGridLineMask|VerticalGridLineMask)|equentialColumnAutoresizingStyle)|NoColumnAutoresizing|UniformColumnAutoresizingStyle|FirstColumnOnlyAutoresizingStyle|LastColumnOnlyAutoresizingStyle)))|rackModeMatrix)|I(?:n(?:sert(?:CharFunctionKey|FunctionKey|LineFunctionKey)|t(?:Type|ernalS(?:criptError|pecifierError))|dexSubelement|validIndexSpecifierError|formational(?:Request|AlertStyle)|PredicateOperatorType)|talicFontMask|SO(?:2022JPStringEncoding|Latin(?:1StringEncoding|2StringEncoding))|dentityMappingCharacterCollection|llegalTextMovement|mage(?:R(?:ight|ep(?:MatchesDevice|LoadStatus(?:ReadingHeader|Completed|InvalidData|Un(?:expectedEOF|knownType)|WillNeedAllData)))|Below|C(?:ellType|ache(?:BySize|Never|Default|Always))|Interpolation(?:High|None|Default|Low)|O(?:nly|verlaps)|Frame(?:Gr(?:oove|ayBezel)|Button|None|Photo)|L(?:oadStatus(?:ReadError|C(?:ompleted|ancelled)|InvalidData|UnexpectedEOF)|eft)|A(?:lign(?:Right|Bottom(?:Right|Left)?|Center|Top(?:Right|Left)?|Left)|bove)))|O(?:n(?:State|eByteGlyphPacking|OffButton|lyScrollerArrows)|ther(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|TextMovement)|SF1OperatingSystem|pe(?:n(?:GL(?:GO(?:Re(?:setLibrary|tainRenderers)|ClearFormatCache|FormatCacheSize)|PFA(?:R(?:obust|endererID)|M(?:inimumPolicy|ulti(?:sample|Screen)|PSafe|aximumPolicy)|BackingStore|S(?:creenMask|te(?:ncilSize|reo)|ingleRenderer|upersample|ample(?:s|Buffers|Alpha))|NoRecovery|C(?:o(?:lor(?:Size|Float)|mpliant)|losestPolicy)|OffScreen|D(?:oubleBuffer|epthSize)|PixelBuffer|VirtualScreenCount|FullScreen|Window|A(?:cc(?:umSize|elerated)|ux(?:Buffers|DepthStencil)|l(?:phaSize|lRenderers))))|StepUnicodeReservedBase)|rationNotSupportedForKeyS(?:criptError|pecifierError))|ffState|KButton|rPredicateType|bjC(?:B(?:itfield|oolType)|S(?:hortType|tr(?:ingType|uctType)|electorType)|NoType|CharType|ObjectType|DoubleType|UnionType|PointerType|VoidType|FloatType|Long(?:Type|longType)|ArrayType))|D(?:i(?:s(?:c(?:losureBezelStyle|reteCapacityLevelIndicatorStyle)|playWindowRunLoopOrdering)|acriticInsensitivePredicateOption|rect(?:Selection|PredicateModifier))|o(?:c(?:ModalWindowMask|ument(?:Directory|ationDirectory))|ubleType|wn(?:TextMovement|ArrowFunctionKey))|e(?:s(?:cendingPageOrder|ktopDirectory)|cimalTabStopType|v(?:ice(?:NColorSpaceModel|IndependentModifierFlagsMask)|eloper(?:Directory|ApplicationDirectory))|fault(?:ControlTint|TokenStyle)|lete(?:Char(?:acter|FunctionKey)|FunctionKey|LineFunctionKey)|moApplicationDirectory)|a(?:yCalendarUnit|teFormatter(?:MediumStyle|Behavior(?:10|Default)|ShortStyle|NoStyle|FullStyle|LongStyle))|ra(?:wer(?:Clos(?:ingState|edState)|Open(?:ingState|State))|gOperation(?:Generic|Move|None|Copy|Delete|Private|Every|Link|All)))|U(?:ser(?:CancelledError|D(?:irectory|omainMask)|FunctionKey)|RL(?:Handle(?:NotLoaded|Load(?:Succeeded|InProgress|Failed))|CredentialPersistence(?:None|Permanent|ForSession))|n(?:scaledWindowMask|cachedRead|i(?:codeStringEncoding|talicFontMask|fiedTitleAndToolbarWindowMask)|d(?:o(?:CloseGroupingRunLoopOrdering|FunctionKey)|e(?:finedDateComponent|rline(?:Style(?:Single|None|Thick|Double)|Pattern(?:Solid|D(?:ot|ash(?:Dot(?:Dot)?)?)))))|known(?:ColorSpaceModel|P(?:ointingDevice|ageOrder)|KeyS(?:criptError|pecifierError))|boldFontMask)|tilityWindowMask|TF8StringEncoding|p(?:dateWindowsRunLoopOrdering|TextMovement|ArrowFunctionKey))|J(?:ustifiedTextAlignment|PEG(?:2000FileType|FileType)|apaneseEUC(?:GlyphPacking|StringEncoding))|P(?:o(?:s(?:t(?:Now|erFontMask|WhenIdle|ASAP)|iti(?:on(?:Replace|Be(?:fore|ginning)|End|After)|ve(?:IntType|DoubleType|FloatType)))|pUp(?:NoArrow|ArrowAt(?:Bottom|Center))|werOffEventType|rtraitOrientation)|NGFileType|ush(?:InCell(?:Mask)?|OnPushOffButton)|e(?:n(?:TipMask|UpperSideMask|PointingDevice|LowerSideMask)|riodic(?:Mask)?)|P(?:S(?:caleField|tatus(?:Title|Field)|aveButton)|N(?:ote(?:Title|Field)|ame(?:Title|Field))|CopiesField|TitleField|ImageButton|OptionsButton|P(?:a(?:perFeedButton|ge(?:Range(?:To|From)|ChoiceMatrix))|reviewButton)|LayoutButton)|lainTextTokenStyle|a(?:useFunctionKey|ragraphSeparatorCharacter|ge(?:DownFunctionKey|UpFunctionKey))|r(?:int(?:ing(?:ReplyLater|Success|Cancelled|Failure)|ScreenFunctionKey|erTable(?:NotFound|OK|Error)|FunctionKey)|o(?:p(?:ertyList(?:XMLFormat|MutableContainers(?:AndLeaves)?|BinaryFormat|Immutable|OpenStepFormat)|rietaryStringEncoding)|gressIndicator(?:BarStyle|SpinningStyle|Preferred(?:SmallThickness|Thickness|LargeThickness|AquaThickness)))|e(?:ssedTab|vFunctionKey))|L(?:HeightForm|CancelButton|TitleField|ImageButton|O(?:KButton|rientationMatrix)|UnitsButton|PaperNameButton|WidthForm))|E(?:n(?:terCharacter|d(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|v(?:e(?:nOddWindingRule|rySubelement)|aluatedObjectExpressionType)|qualTo(?:Comparison|PredicateOperatorType)|ra(?:serPointingDevice|CalendarUnit|DatePickerElementFlag)|x(?:clude(?:10|QuickDrawElementsIconCreationOption)|pandedFontMask|ecuteFunctionKey))|V(?:i(?:ew(?:M(?:in(?:XMargin|YMargin)|ax(?:XMargin|YMargin))|HeightSizable|NotSizable|WidthSizable)|aPanelFontAction)|erticalRuler|a(?:lidationErrorM(?:inimum|aximum)|riableExpressionType))|Key(?:SpecifierEvaluationScriptError|Down(?:Mask)?|Up(?:Mask)?|PathExpressionType|Value(?:MinusSetMutation|SetSetMutation|Change(?:Re(?:placement|moval)|Setting|Insertion)|IntersectSetMutation|ObservingOption(?:New|Old)|UnionSetMutation|ValidationError))|QTMovie(?:NormalPlayback|Looping(?:BackAndForthPlayback|Playback))|F(?:1(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|7FunctionKey|i(?:nd(?:PanelAction(?:Replace(?:A(?:ndFind|ll(?:InSelection)?))?|S(?:howFindPanel|e(?:tFindString|lectAll(?:InSelection)?))|Next|Previous)|FunctionKey)|tPagination|le(?:Read(?:No(?:SuchFileError|PermissionError)|CorruptFileError|In(?:validFileNameError|applicableStringEncodingError)|Un(?:supportedSchemeError|knownError))|HandlingPanel(?:CancelButton|OKButton)|NoSuchFileError|ErrorM(?:inimum|aximum)|Write(?:NoPermissionError|In(?:validFileNameError|applicableStringEncodingError)|OutOfSpaceError|Un(?:supportedSchemeError|knownError))|LockingError)|xedPitchFontMask)|2(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|o(?:nt(?:Mo(?:noSpaceTrait|dernSerifsClass)|BoldTrait|S(?:ymbolicClass|criptsClass|labSerifsClass|ansSerifClass)|C(?:o(?:ndensedTrait|llectionApplicationOnlyMask)|larendonSerifsClass)|TransitionalSerifsClass|I(?:ntegerAdvancementsRenderingMode|talicTrait)|O(?:ldStyleSerifsClass|rnamentalsClass)|DefaultRenderingMode|U(?:nknownClass|IOptimizedTrait)|Panel(?:S(?:hadowEffectModeMask|t(?:andardModesMask|rikethroughEffectModeMask)|izeModeMask)|CollectionModeMask|TextColorEffectModeMask|DocumentColorEffectModeMask|UnderlineEffectModeMask|FaceModeMask|All(?:ModesMask|EffectsModeMask))|ExpandedTrait|VerticalTrait|F(?:amilyClassMask|reeformSerifsClass)|Antialiased(?:RenderingMode|IntegerAdvancementsRenderingMode))|cusRing(?:Below|Type(?:None|Default|Exterior)|Only|Above)|urByteGlyphPacking|rm(?:attingError(?:M(?:inimum|aximum))?|FeedCharacter))|8FunctionKey|unction(?:ExpressionType|KeyMask)|3(?:1FunctionKey|2FunctionKey|3FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey)|9FunctionKey|4FunctionKey|P(?:RevertButton|S(?:ize(?:Title|Field)|etButton)|CurrentField|Preview(?:Button|Field))|l(?:oat(?:ingPointSamplesBitmapFormat|Type)|agsChanged(?:Mask)?)|axButton|5FunctionKey|6FunctionKey)|W(?:heelModeColorPanel|indow(?:s(?:NTOperatingSystem|CP125(?:1StringEncoding|2StringEncoding|3StringEncoding|4StringEncoding|0StringEncoding)|95(?:InterfaceStyle|OperatingSystem))|M(?:iniaturizeButton|ovedEventType)|Below|CloseButton|ToolbarButton|ZoomButton|Out|DocumentIconButton|ExposedEventType|Above)|orkspaceLaunch(?:NewInstance|InhibitingBackgroundOnly|Default|PreferringClassic|WithoutA(?:ctivation|ddingToRecents)|A(?:sync|nd(?:Hide(?:Others)?|Print)|llowingClassicStartup))|eek(?:day(?:CalendarUnit|OrdinalCalendarUnit)|CalendarUnit)|a(?:ntsBidiLevels|rningAlertStyle)|r(?:itingDirection(?:RightToLeft|Natural|LeftToRight)|apCalendarComponents))|L(?:i(?:stModeMatrix|ne(?:Moves(?:Right|Down|Up|Left)|B(?:order|reakBy(?:C(?:harWrapping|lipping)|Truncating(?:Middle|Head|Tail)|WordWrapping))|S(?:eparatorCharacter|weep(?:Right|Down|Up|Left))|ToBezierPathElement|DoesntMove|arSlider)|teralSearch|kePredicateOperatorType|ghterFontAction|braryDirectory)|ocalDomainMask|e(?:ssThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|ft(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey))|a(?:yout(?:RightToLeft|NotDone|CantFit|OutOfGlyphs|Done|LeftToRight)|ndscapeOrientation)|ABColorSpaceModel)|A(?:sc(?:iiWithDoubleByteEUCGlyphPacking|endingPageOrder)|n(?:y(?:Type|PredicateModifier|EventMask)|choredSearch|imation(?:Blocking|Nonblocking(?:Threaded)?|E(?:ffect(?:DisappearingItemDefault|Poof)|ase(?:In(?:Out)?|Out))|Linear)|dPredicateType)|t(?:Bottom|tachmentCharacter|omicWrite|Top)|SCIIStringEncoding|d(?:obe(?:GB1CharacterCollection|CNS1CharacterCollection|Japan(?:1CharacterCollection|2CharacterCollection)|Korea1CharacterCollection)|dTraitFontAction|minApplicationDirectory)|uto(?:saveOperation|Pagination)|pp(?:lication(?:SupportDirectory|D(?:irectory|e(?:fined(?:Mask)?|legateReply(?:Success|Cancel|Failure)|activatedEventType))|ActivatedEventType)|KitDefined(?:Mask)?)|l(?:ternateKeyMask|pha(?:ShiftKeyMask|NonpremultipliedBitmapFormat|FirstBitmapFormat)|ert(?:SecondButtonReturn|ThirdButtonReturn|OtherReturn|DefaultReturn|ErrorReturn|FirstButtonReturn|AlternateReturn)|l(?:ScrollerParts|DomainsMask|PredicateModifier|LibrariesDirectory|ApplicationsDirectory))|rgument(?:sWrongScriptError|EvaluationScriptError)|bove(?:Bottom|Top)|WTEventType)))(?:\\b)"},{token:"support.function.C99.c",regex:s.cFunctions},{token:n.getKeywords(),regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"punctuation.section.scope.begin.objc",regex:"\\[",next:"bracketed_content"},{token:"meta.function.objc",regex:"^(?:-|\\+)\\s*"}],constant_NSString:[{token:"constant.character.escape.objc",regex:e},{token:"invalid.illegal.unknown-escape.objc",regex:"\\\\."},{token:"string",regex:'[^"\\\\]+'},{token:"punctuation.definition.string.end",regex:'"',next:"start"}],protocol_list:[{token:"punctuation.section.scope.end.objc",regex:">",next:"start"},{token:"support.other.protocol.objc",regex:"\bNS(?:GlyphStorage|M(?:utableCopying|enuItem)|C(?:hangeSpelling|o(?:ding|pying|lorPicking(?:Custom|Default)))|T(?:oolbarItemValidations|ext(?:Input|AttachmentCell))|I(?:nputServ(?:iceProvider|erMouseTracker)|gnoreMisspelledWords)|Obj(?:CTypeSerializationCallBack|ect)|D(?:ecimalNumberBehaviors|raggingInfo)|U(?:serInterfaceValidations|RL(?:HandleClient|DownloadDelegate|ProtocolClient|AuthenticationChallengeSender))|Validated(?:ToobarItem|UserInterfaceItem)|Locking)\b"}],selectors:[{token:"support.function.any-method.name-of-parameter.objc",regex:"\\b(?:[a-zA-Z_:][\\w]*)+"},{token:"punctuation",regex:"\\)",next:"start"}],bracketed_content:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:["support.function.any-method.objc"],regex:"(?:predicateWithFormat:| NSPredicate predicateWithFormat:)",next:"start"},{token:"support.function.any-method.objc",regex:"\\w+(?::|(?=]))",next:"start"}],bracketed_strings:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:"keyword.operator.logical.predicate.cocoa",regex:"\\b(?:AND|OR|NOT|IN)\\b"},{token:["invalid.illegal.unknown-method.objc","punctuation.separator.arguments.objc"],regex:"\\b(\\w+)(:)"},{regex:"\\b(?:ALL|ANY|SOME|NONE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:NULL|NIL|SELF|TRUE|YES|FALSE|NO|FIRST|LAST|SIZE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:MATCHES|CONTAINS|BEGINSWITH|ENDSWITH|BETWEEN)\\b",token:"keyword.operator.comparison.predicate.cocoa"},{regex:"\\bC(?:ASEINSENSITIVE|I)\\b",token:"keyword.other.modifier.predicate.cocoa"},{regex:"\\b(?:ANYKEY|SUBQUERY|CAST|TRUEPREDICATE|FALSEPREDICATE)\\b",token:"keyword.other.predicate.cocoa"},{regex:e,token:"constant.character.escape.objc"},{regex:"\\\\.",token:"invalid.illegal.unknown-escape.objc"},{token:"string",regex:'[^"\\\\]'},{token:"punctuation.definition.string.end.objc",regex:'"',next:"predicates"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],methods:[{token:"meta.function.objc",regex:"(?=\\{|#)|;",next:"start"}]};for(var u in r)this.$rules[u]?this.$rules[u].push&&this.$rules[u].push.apply(this.$rules[u],r[u]):this.$rules[u]=r[u];this.$rules.bracketed_content=this.$rules.bracketed_content.concat(this.$rules.start,t),this.embedRules(i,"doc-",[i.getEndRule("start")])};r.inherits(u,o),t.ObjectiveCHighlightRules=u}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/objectivec",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/objectivec_highlight_rules","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./objectivec_highlight_rules").ObjectiveCHighlightRules,o=e("./folding/cstyle").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$id="ace/mode/objectivec"}.call(u.prototype),t.Mode=u}) \ No newline at end of file +define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/c_cpp_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o=t.cFunctions="\\b(?:hypot(?:f|l)?|s(?:scanf|ystem|nprintf|ca(?:nf|lb(?:n(?:f|l)?|ln(?:f|l)?))|i(?:n(?:h(?:f|l)?|f|l)?|gn(?:al|bit))|tr(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?)|error|pbrk|ftime|len|rchr|xfrm)|printf|et(?:jmp|vbuf|locale|buf)|qrt(?:f|l)?|w(?:scanf|printf)|rand)|n(?:e(?:arbyint(?:f|l)?|xt(?:toward(?:f|l)?|after(?:f|l)?))|an(?:f|l)?)|c(?:s(?:in(?:h(?:f|l)?|f|l)?|qrt(?:f|l)?)|cos(?:h(?:f)?|f|l)?|imag(?:f|l)?|t(?:ime|an(?:h(?:f|l)?|f|l)?)|o(?:s(?:h(?:f|l)?|f|l)?|nj(?:f|l)?|pysign(?:f|l)?)|p(?:ow(?:f|l)?|roj(?:f|l)?)|e(?:il(?:f|l)?|xp(?:f|l)?)|l(?:o(?:ck|g(?:f|l)?)|earerr)|a(?:sin(?:h(?:f|l)?|f|l)?|cos(?:h(?:f|l)?|f|l)?|tan(?:h(?:f|l)?|f|l)?|lloc|rg(?:f|l)?|bs(?:f|l)?)|real(?:f|l)?|brt(?:f|l)?)|t(?:ime|o(?:upper|lower)|an(?:h(?:f|l)?|f|l)?|runc(?:f|l)?|gamma(?:f|l)?|mp(?:nam|file))|i(?:s(?:space|n(?:ormal|an)|cntrl|inf|digit|u(?:nordered|pper)|p(?:unct|rint)|finite|w(?:space|c(?:ntrl|type)|digit|upper|p(?:unct|rint)|lower|al(?:num|pha)|graph|xdigit|blank)|l(?:ower|ess(?:equal|greater)?)|al(?:num|pha)|gr(?:eater(?:equal)?|aph)|xdigit|blank)|logb(?:f|l)?|max(?:div|abs))|di(?:v|fftime)|_Exit|unget(?:c|wc)|p(?:ow(?:f|l)?|ut(?:s|c(?:har)?|wc(?:har)?)|error|rintf)|e(?:rf(?:c(?:f|l)?|f|l)?|x(?:it|p(?:2(?:f|l)?|f|l|m1(?:f|l)?)?))|v(?:s(?:scanf|nprintf|canf|printf|w(?:scanf|printf))|printf|f(?:scanf|printf|w(?:scanf|printf))|w(?:scanf|printf)|a_(?:start|copy|end|arg))|qsort|f(?:s(?:canf|e(?:tpos|ek))|close|tell|open|dim(?:f|l)?|p(?:classify|ut(?:s|c|w(?:s|c))|rintf)|e(?:holdexcept|set(?:e(?:nv|xceptflag)|round)|clearexcept|testexcept|of|updateenv|r(?:aiseexcept|ror)|get(?:e(?:nv|xceptflag)|round))|flush|w(?:scanf|ide|printf|rite)|loor(?:f|l)?|abs(?:f|l)?|get(?:s|c|pos|w(?:s|c))|re(?:open|e|ad|xp(?:f|l)?)|m(?:in(?:f|l)?|od(?:f|l)?|a(?:f|l|x(?:f|l)?)?))|l(?:d(?:iv|exp(?:f|l)?)|o(?:ngjmp|cal(?:time|econv)|g(?:1(?:p(?:f|l)?|0(?:f|l)?)|2(?:f|l)?|f|l|b(?:f|l)?)?)|abs|l(?:div|abs|r(?:int(?:f|l)?|ound(?:f|l)?))|r(?:int(?:f|l)?|ound(?:f|l)?)|gamma(?:f|l)?)|w(?:scanf|c(?:s(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?|mbs)|pbrk|ftime|len|r(?:chr|tombs)|xfrm)|to(?:b|mb)|rtomb)|printf|mem(?:set|c(?:hr|py|mp)|move))|a(?:s(?:sert|ctime|in(?:h(?:f|l)?|f|l)?)|cos(?:h(?:f|l)?|f|l)?|t(?:o(?:i|f|l(?:l)?)|exit|an(?:h(?:f|l)?|2(?:f|l)?|f|l)?)|b(?:s|ort))|g(?:et(?:s|c(?:har)?|env|wc(?:har)?)|mtime)|r(?:int(?:f|l)?|ound(?:f|l)?|e(?:name|alloc|wind|m(?:ove|quo(?:f|l)?|ainder(?:f|l)?))|a(?:nd|ise))|b(?:search|towc)|m(?:odf(?:f|l)?|em(?:set|c(?:hr|py|mp)|move)|ktime|alloc|b(?:s(?:init|towcs|rtowcs)|towc|len|r(?:towc|len))))\\b",u=function(){var e="break|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while|catch|operator|try|throw|using",t="asm|__asm__|auto|bool|_Bool|char|_Complex|double|enum|float|_Imaginary|int|long|short|signed|struct|typedef|union|unsigned|void|class|wchar_t|template|char16_t|char32_t",n="const|extern|register|restrict|static|volatile|inline|private|protected|public|friend|explicit|virtual|export|mutable|typename|constexpr|new|delete|alignas|alignof|decltype|noexcept|thread_local",r="and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eqconst_cast|dynamic_cast|reinterpret_cast|static_cast|sizeof|namespace",s="NULL|true|false|TRUE|FALSE|nullptr",u=this.$keywords=this.createKeywordMapper({"keyword.control":e,"storage.type":t,"storage.modifier":n,"keyword.operator":r,"variable.language":"this","constant.language":s},"identifier"),a="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b",f=/\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}U[a-fA-F\d]{8}|.)/.source;this.$rules={start:[{token:"comment",regex:"//$",next:"start"},{token:"comment",regex:"//",next:"singleLineComment"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:"string",regex:"'(?:"+f+"|.)?'"},{token:"string.start",regex:'"',stateName:"qqstring",next:[{token:"string",regex:/\\\s*$/,next:"qqstring"},{token:"constant.language.escape",regex:f},{token:"constant.language.escape",regex:/%[^'"\\]/},{token:"string.end",regex:'"|$',next:"start"},{defaultToken:"string"}]},{token:"string.start",regex:'R"\\(',stateName:"rawString",next:[{token:"string.end",regex:'\\)"',next:"start"},{defaultToken:"string"}]},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"keyword",regex:"#\\s*(?:include|import|pragma|line|define|undef)\\b",next:"directive"},{token:"keyword",regex:"#\\s*(?:endif|if|ifdef|else|elif|ifndef)\\b"},{token:"support.function.C99.c",regex:o},{token:u,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*"},{token:"keyword.operator",regex:/--|\+\+|<<=|>>=|>>>=|<>|&&|\|\||\?:|[*%\/+\-&\^|~!<>=]=?/},{token:"punctuation.operator",regex:"\\?|\\:|\\,|\\;|\\."},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],singleLineComment:[{token:"comment",regex:/\\$/,next:"singleLineComment"},{token:"comment",regex:/$/,next:"start"},{defaultToken:"comment"}],directive:[{token:"constant.other.multiline",regex:/\\/},{token:"constant.other.multiline",regex:/.*\\/},{token:"constant.other",regex:"\\s*<.+?>",next:"start"},{token:"constant.other",regex:'\\s*["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',next:"start"},{token:"constant.other",regex:"\\s*['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",next:"start"},{token:"constant.other",regex:/[^\\\/]+/,next:"start"}]},this.embedRules(i,"doc-",[i.getEndRule("start")]),this.normalizeRules()};r.inherits(u,s),t.c_cppHighlightRules=u}),define("ace/mode/objectivec_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/c_cpp_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./c_cpp_highlight_rules"),o=s.c_cppHighlightRules,u=function(){var e="\\\\(?:[abefnrtv'\"?\\\\]|[0-3]\\d{1,2}|[4-7]\\d?|222|x[a-zA-Z0-9]+)",t=[{regex:"\\b_cmd\\b",token:"variable.other.selector.objc"},{regex:"\\b(?:self|super)\\b",token:"variable.language.objc"}],n=new o,r=n.getRules();this.$rules={start:[{token:"comment",regex:"\\/\\/.*$"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:["storage.type.objc","punctuation.definition.storage.type.objc","entity.name.type.objc","text","entity.other.inherited-class.objc"],regex:"(@)(interface|protocol)(?!.+;)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*)([A-Za-z]+)"},{token:["storage.type.objc"],regex:"(@end)"},{token:["storage.type.objc","entity.name.type.objc","entity.other.inherited-class.objc"],regex:"(@implementation)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*?::\\s*(?:[A-Za-z][A-Za-z0-9]*))?"},{token:"string.begin.objc",regex:'@"',next:"constant_NSString"},{token:"storage.type.objc",regex:"\\bid\\s*<",next:"protocol_list"},{token:"keyword.control.macro.objc",regex:"\\bNS_DURING|NS_HANDLER|NS_ENDHANDLER\\b"},{token:["punctuation.definition.keyword.objc","keyword.control.exception.objc"],regex:"(@)(try|catch|finally|throw)\\b"},{token:["punctuation.definition.keyword.objc","keyword.other.objc"],regex:"(@)(defs|encode)\\b"},{token:["storage.type.id.objc","text"],regex:"(\\bid\\b)(\\s|\\n)?"},{token:"storage.type.objc",regex:"\\bIBOutlet|IBAction|BOOL|SEL|id|unichar|IMP|Class\\b"},{token:["punctuation.definition.storage.type.objc","storage.type.objc"],regex:"(@)(class|protocol)\\b"},{token:["punctuation.definition.storage.type.objc","punctuation"],regex:"(@selector)(\\s*\\()",next:"selectors"},{token:["punctuation.definition.storage.modifier.objc","storage.modifier.objc"],regex:"(@)(synchronized|public|private|protected|package)\\b"},{token:"constant.language.objc",regex:"\\bYES|NO|Nil|nil\\b"},{token:"support.variable.foundation",regex:"\\bNSApp\\b"},{token:["support.function.cocoa.leopard"],regex:"(?:\\b)(NS(?:Rect(?:ToCGRect|FromCGRect)|MakeCollectable|S(?:tringFromProtocol|ize(?:ToCGSize|FromCGSize))|Draw(?:NinePartImage|ThreePartImage)|P(?:oint(?:ToCGPoint|FromCGPoint)|rotocolFromString)|EventMaskFromType|Value))(?:\\b)"},{token:["support.function.cocoa"],regex:"(?:\\b)(NS(?:R(?:ound(?:DownToMultipleOfPageSize|UpToMultipleOfPageSize)|un(?:CriticalAlertPanel(?:RelativeToWindow)?|InformationalAlertPanel(?:RelativeToWindow)?|AlertPanel(?:RelativeToWindow)?)|e(?:set(?:MapTable|HashTable)|c(?:ycleZone|t(?:Clip(?:List)?|F(?:ill(?:UsingOperation|List(?:UsingOperation|With(?:Grays|Colors(?:UsingOperation)?))?)?|romString))|ordAllocationEvent)|turnAddress|leaseAlertPanel|a(?:dPixel|l(?:MemoryAvailable|locateCollectable))|gisterServicesProvider)|angeFromString)|Get(?:SizeAndAlignment|CriticalAlertPanel|InformationalAlertPanel|UncaughtExceptionHandler|FileType(?:s)?|WindowServerMemory|AlertPanel)|M(?:i(?:n(?:X|Y)|d(?:X|Y))|ouseInRect|a(?:p(?:Remove|Get|Member|Insert(?:IfAbsent|KnownAbsent)?)|ke(?:R(?:ect|ange)|Size|Point)|x(?:Range|X|Y)))|B(?:itsPer(?:SampleFromDepth|PixelFromDepth)|e(?:stDepth|ep|gin(?:CriticalAlertSheet|InformationalAlertSheet|AlertSheet)))|S(?:ho(?:uldRetainWithZone|w(?:sServicesMenuItem|AnimationEffect))|tringFrom(?:R(?:ect|ange)|MapTable|S(?:ize|elector)|HashTable|Class|Point)|izeFromString|e(?:t(?:ShowsServicesMenuItem|ZoneName|UncaughtExceptionHandler|FocusRingStyle)|lectorFromString|archPathForDirectoriesInDomains)|wap(?:Big(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|Short|Host(?:ShortTo(?:Big|Little)|IntTo(?:Big|Little)|DoubleTo(?:Big|Little)|FloatTo(?:Big|Little)|Long(?:To(?:Big|Little)|LongTo(?:Big|Little)))|Int|Double|Float|L(?:ittle(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|ong(?:Long)?)))|H(?:ighlightRect|o(?:stByteOrder|meDirectory(?:ForUser)?)|eight|ash(?:Remove|Get|Insert(?:IfAbsent|KnownAbsent)?)|FSType(?:CodeFromFileType|OfFile))|N(?:umberOfColorComponents|ext(?:MapEnumeratorPair|HashEnumeratorItem))|C(?:o(?:n(?:tainsRect|vert(?:GlyphsToPackedGlyphs|Swapped(?:DoubleToHost|FloatToHost)|Host(?:DoubleToSwapped|FloatToSwapped)))|unt(?:MapTable|HashTable|Frames|Windows(?:ForContext)?)|py(?:M(?:emoryPages|apTableWithZone)|Bits|HashTableWithZone|Object)|lorSpaceFromDepth|mpare(?:MapTables|HashTables))|lassFromString|reate(?:MapTable(?:WithZone)?|HashTable(?:WithZone)?|Zone|File(?:namePboardType|ContentsPboardType)))|TemporaryDirectory|I(?:s(?:ControllerMarker|EmptyRect|FreedObject)|n(?:setRect|crementExtraRefCount|te(?:r(?:sect(?:sRect|ionR(?:ect|ange))|faceStyleForKey)|gralRect)))|Zone(?:Realloc|Malloc|Name|Calloc|Fr(?:omPointer|ee))|O(?:penStepRootDirectory|ffsetRect)|D(?:i(?:sableScreenUpdates|videRect)|ottedFrameRect|e(?:c(?:imal(?:Round|Multiply|S(?:tring|ubtract)|Normalize|Co(?:py|mpa(?:ct|re))|IsNotANumber|Divide|Power|Add)|rementExtraRefCountWasZero)|faultMallocZone|allocate(?:MemoryPages|Object))|raw(?:Gr(?:oove|ayBezel)|B(?:itmap|utton)|ColorTiledRects|TiledRects|DarkBezel|W(?:hiteBezel|indowBackground)|LightBezel))|U(?:serName|n(?:ionR(?:ect|ange)|registerServicesProvider)|pdateDynamicServices)|Java(?:Bundle(?:Setup|Cleanup)|Setup(?:VirtualMachine)?|Needs(?:ToLoadClasses|VirtualMachine)|ClassesF(?:orBundle|romPath)|ObjectNamedInPath|ProvidesClasses)|P(?:oint(?:InRect|FromString)|erformService|lanarFromDepth|ageSize)|E(?:n(?:d(?:MapTableEnumeration|HashTableEnumeration)|umerate(?:MapTable|HashTable)|ableScreenUpdates)|qual(?:R(?:ects|anges)|Sizes|Points)|raseRect|xtraRefCount)|F(?:ileTypeForHFSTypeCode|ullUserName|r(?:ee(?:MapTable|HashTable)|ame(?:Rect(?:WithWidth(?:UsingOperation)?)?|Address)))|Wi(?:ndowList(?:ForContext)?|dth)|Lo(?:cationInRange|g(?:v|PageSize)?)|A(?:ccessibility(?:R(?:oleDescription(?:ForUIElement)?|aiseBadArgumentException)|Unignored(?:Children(?:ForOnlyChild)?|Descendant|Ancestor)|PostNotification|ActionDescription)|pplication(?:Main|Load)|vailableWindowDepths|ll(?:MapTable(?:Values|Keys)|HashTableObjects|ocate(?:MemoryPages|Collectable|Object)))))(?:\\b)"},{token:["support.class.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor|G(?:arbageCollector|radient)|MapTable|HashTable|Co(?:ndition|llectionView(?:Item)?)|T(?:oolbarItemGroup|extInputClient|r(?:eeNode|ackingArea))|InvocationOperation|Operation(?:Queue)?|D(?:ictionaryController|ockTile)|P(?:ointer(?:Functions|Array)|athC(?:o(?:ntrol(?:Delegate)?|mponentCell)|ell(?:Delegate)?)|r(?:intPanelAccessorizing|edicateEditor(?:RowTemplate)?))|ViewController|FastEnumeration|Animat(?:ionContext|ablePropertyContainer)))(?:\\b)"},{token:["support.class.cocoa"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.type.cocoa.leopard"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.class.quartz"],regex:"(?:\\b)(C(?:I(?:Sampler|Co(?:ntext|lor)|Image(?:Accumulator)?|PlugIn(?:Registration)?|Vector|Kernel|Filter(?:Generator|Shape)?)|A(?:Renderer|MediaTiming(?:Function)?|BasicAnimation|ScrollLayer|Constraint(?:LayoutManager)?|T(?:iledLayer|extLayer|rans(?:ition|action))|OpenGLLayer|PropertyAnimation|KeyframeAnimation|Layer|A(?:nimation(?:Group)?|ction))))(?:\\b)"},{token:["support.type.quartz"],regex:"(?:\\b)(C(?:G(?:Float|Point|Size|Rect)|IFormat|AConstraintAttribute))(?:\\b)"},{token:["support.type.cocoa"],regex:"(?:\\b)(NS(?:R(?:ect(?:Edge)?|ange)|G(?:lyph(?:Relation|LayoutMode)?|radientType)|M(?:odalSession|a(?:trixMode|p(?:Table|Enumerator)))|B(?:itmapImageFileType|orderType|uttonType|ezelStyle|ackingStoreType|rowserColumnResizingType)|S(?:cr(?:oll(?:er(?:Part|Arrow)|ArrowPosition)|eenAuxiliaryOpaque)|tringEncoding|ize|ocketNativeHandle|election(?:Granularity|Direction|Affinity)|wapped(?:Double|Float)|aveOperationType)|Ha(?:sh(?:Table|Enumerator)|ndler(?:2)?)|C(?:o(?:ntrol(?:Size|Tint)|mp(?:ositingOperation|arisonResult))|ell(?:State|Type|ImagePosition|Attribute))|T(?:hreadPrivate|ypesetterGlyphInfo|i(?:ckMarkPosition|tlePosition|meInterval)|o(?:ol(?:TipTag|bar(?:SizeMode|DisplayMode))|kenStyle)|IFFCompression|ext(?:TabType|Alignment)|ab(?:State|leViewDropOperation|ViewType)|rackingRectTag)|ImageInterpolation|Zone|OpenGL(?:ContextAuxiliary|PixelFormatAuxiliary)|D(?:ocumentChangeType|atePickerElementFlags|ra(?:werState|gOperation))|UsableScrollerParts|P(?:oint|r(?:intingPageOrder|ogressIndicator(?:Style|Th(?:ickness|readInfo))))|EventType|KeyValueObservingOptions|Fo(?:nt(?:SymbolicTraits|TraitMask|Action)|cusRingType)|W(?:indow(?:OrderingMode|Depth)|orkspace(?:IconCreationOptions|LaunchOptions)|ritingDirection)|L(?:ineBreakMode|ayout(?:Status|Direction))|A(?:nimation(?:Progress|Effect)|ppl(?:ication(?:TerminateReply|DelegateReply|PrintReply)|eEventManagerSuspensionID)|ffineTransformStruct|lertStyle)))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:NotFound|Ordered(?:Ascending|Descending|Same)))(?:\\b)"},{token:["support.constant.notification.cocoa.leopard"],regex:"(?:\\b)(NS(?:MenuDidBeginTracking|ViewDidUpdateTrackingAreas)?Notification)(?:\\b)"},{token:["support.constant.notification.cocoa"],regex:"(?:\\b)(NS(?:Menu(?:Did(?:RemoveItem|SendAction|ChangeItem|EndTracking|AddItem)|WillSendAction)|S(?:ystemColorsDidChange|plitView(?:DidResizeSubviews|WillResizeSubviews))|C(?:o(?:nt(?:extHelpModeDid(?:Deactivate|Activate)|rolT(?:intDidChange|extDid(?:BeginEditing|Change|EndEditing)))|lor(?:PanelColorDidChange|ListDidChange)|mboBox(?:Selection(?:IsChanging|DidChange)|Will(?:Dismiss|PopUp)))|lassDescriptionNeededForClass)|T(?:oolbar(?:DidRemoveItem|WillAddItem)|ext(?:Storage(?:DidProcessEditing|WillProcessEditing)|Did(?:BeginEditing|Change|EndEditing)|View(?:DidChange(?:Selection|TypingAttributes)|WillChangeNotifyingTextView))|ableView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)))|ImageRepRegistryDidChange|OutlineView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)|Item(?:Did(?:Collapse|Expand)|Will(?:Collapse|Expand)))|Drawer(?:Did(?:Close|Open)|Will(?:Close|Open))|PopUpButton(?:CellWillPopUp|WillPopUp)|View(?:GlobalFrameDidChange|BoundsDidChange|F(?:ocusDidChange|rameDidChange))|FontSetChanged|W(?:indow(?:Did(?:Resi(?:ze|gn(?:Main|Key))|M(?:iniaturize|ove)|Become(?:Main|Key)|ChangeScreen(?:|Profile)|Deminiaturize|Update|E(?:ndSheet|xpose))|Will(?:M(?:iniaturize|ove)|BeginSheet|Close))|orkspace(?:SessionDid(?:ResignActive|BecomeActive)|Did(?:Mount|TerminateApplication|Unmount|PerformFileOperation|Wake|LaunchApplication)|Will(?:Sleep|Unmount|PowerOff|LaunchApplication)))|A(?:ntialiasThresholdChanged|ppl(?:ication(?:Did(?:ResignActive|BecomeActive|Hide|ChangeScreenParameters|U(?:nhide|pdate)|FinishLaunching)|Will(?:ResignActive|BecomeActive|Hide|Terminate|U(?:nhide|pdate)|FinishLaunching))|eEventManagerWillProcessFirstEvent)))Notification)(?:\\b)"},{token:["support.constant.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor(?:RowType(?:Simple|Compound)|NestingMode(?:Si(?:ngle|mple)|Compound|List))|GradientDraws(?:BeforeStartingLocation|AfterEndingLocation)|M(?:inusSetExpressionType|a(?:chPortDeallocate(?:ReceiveRight|SendRight|None)|pTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality)))|B(?:oxCustom|undleExecutableArchitecture(?:X86|I386|PPC(?:64)?)|etweenPredicateOperatorType|ackgroundStyle(?:Raised|Dark|L(?:ight|owered)))|S(?:tring(?:DrawingTruncatesLastVisibleLine|EncodingConversion(?:ExternalRepresentation|AllowLossy))|ubqueryExpressionType|p(?:e(?:ech(?:SentenceBoundary|ImmediateBoundary|WordBoundary)|llingState(?:GrammarFlag|SpellingFlag))|litViewDividerStyleThi(?:n|ck))|e(?:rvice(?:RequestTimedOutError|M(?:iscellaneousError|alformedServiceDictionaryError)|InvalidPasteboardDataError|ErrorM(?:inimum|aximum)|Application(?:NotFoundError|LaunchFailedError))|gmentStyle(?:Round(?:Rect|ed)|SmallSquare|Capsule|Textured(?:Rounded|Square)|Automatic)))|H(?:UDWindowMask|ashTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality))|N(?:oModeColorPanel|etServiceNoAutoRename)|C(?:hangeRedone|o(?:ntainsPredicateOperatorType|l(?:orRenderingIntent(?:RelativeColorimetric|Saturation|Default|Perceptual|AbsoluteColorimetric)|lectorDisabledOption))|ellHit(?:None|ContentArea|TrackableArea|EditableTextArea))|T(?:imeZoneNameStyle(?:S(?:hort(?:Standard|DaylightSaving)|tandard)|DaylightSaving)|extFieldDatePickerStyle|ableViewSelectionHighlightStyle(?:Regular|SourceList)|racking(?:Mouse(?:Moved|EnteredAndExited)|CursorUpdate|InVisibleRect|EnabledDuringMouseDrag|A(?:ssumeInside|ctive(?:In(?:KeyWindow|ActiveApp)|WhenFirstResponder|Always))))|I(?:n(?:tersectSetExpressionType|dexedColorSpaceModel)|mageScale(?:None|Proportionally(?:Down|UpOrDown)|AxesIndependently))|Ope(?:nGLPFAAllowOfflineRenderers|rationQueue(?:DefaultMaxConcurrentOperationCount|Priority(?:High|Normal|Very(?:High|Low)|Low)))|D(?:iacriticInsensitiveSearch|ownloadsDirectory)|U(?:nionSetExpressionType|TF(?:16(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)|32(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)))|P(?:ointerFunctions(?:Ma(?:chVirtualMemory|llocMemory)|Str(?:ongMemory|uctPersonality)|C(?:StringPersonality|opyIn)|IntegerPersonality|ZeroingWeakMemory|O(?:paque(?:Memory|Personality)|bjectP(?:ointerPersonality|ersonality)))|at(?:hStyle(?:Standard|NavigationBar|PopUp)|ternColorSpaceModel)|rintPanelShows(?:Scaling|Copies|Orientation|P(?:a(?:perSize|ge(?:Range|SetupAccessory))|review)))|Executable(?:RuntimeMismatchError|NotLoadableError|ErrorM(?:inimum|aximum)|L(?:inkError|oadError)|ArchitectureMismatchError)|KeyValueObservingOption(?:Initial|Prior)|F(?:i(?:ndPanelSubstringMatchType(?:StartsWith|Contains|EndsWith|FullWord)|leRead(?:TooLargeError|UnknownStringEncodingError))|orcedOrderingSearch)|Wi(?:ndow(?:BackingLocation(?:MainMemory|Default|VideoMemory)|Sharing(?:Read(?:Only|Write)|None)|CollectionBehavior(?:MoveToActiveSpace|CanJoinAllSpaces|Default))|dthInsensitiveSearch)|AggregateExpressionType))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:R(?:GB(?:ModeColorPanel|ColorSpaceModel)|ight(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey)|ound(?:RectBezelStyle|Bankers|ed(?:BezelStyle|TokenStyle|DisclosureBezelStyle)|Down|Up|Plain|Line(?:CapStyle|JoinStyle))|un(?:StoppedResponse|ContinuesResponse|AbortedResponse)|e(?:s(?:izableWindowMask|et(?:CursorRectsRunLoopOrdering|FunctionKey))|ce(?:ssedBezelStyle|iver(?:sCantHandleCommandScriptError|EvaluationScriptError))|turnTextMovement|doFunctionKey|quiredArgumentsMissingScriptError|l(?:evancyLevelIndicatorStyle|ative(?:Before|After))|gular(?:SquareBezelStyle|ControlSize)|moveTraitFontAction)|a(?:n(?:domSubelement|geDateMode)|tingLevelIndicatorStyle|dio(?:ModeMatrix|Button)))|G(?:IFFileType|lyph(?:Below|Inscribe(?:B(?:elow|ase)|Over(?:strike|Below)|Above)|Layout(?:WithPrevious|A(?:tAPoint|gainstAPoint))|A(?:ttribute(?:BidiLevel|Soft|Inscribe|Elastic)|bove))|r(?:ooveBorder|eaterThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|a(?:y(?:ModeColorPanel|ColorSpaceModel)|dient(?:None|Con(?:cave(?:Strong|Weak)|vex(?:Strong|Weak)))|phiteControlTint)))|XML(?:N(?:o(?:tationDeclarationKind|de(?:CompactEmptyElement|IsCDATA|OptionsNone|Use(?:SingleQuotes|DoubleQuotes)|Pre(?:serve(?:NamespaceOrder|C(?:haracterReferences|DATA)|DTD|Prefixes|E(?:ntities|mptyElements)|Quotes|Whitespace|A(?:ttributeOrder|ll))|ttyPrint)|ExpandEmptyElement))|amespaceKind)|CommentKind|TextKind|InvalidKind|D(?:ocument(?:X(?:MLKind|HTMLKind|Include)|HTMLKind|T(?:idy(?:XML|HTML)|extKind)|IncludeContentTypeDeclaration|Validate|Kind)|TDKind)|P(?:arser(?:GTRequiredError|XMLDeclNot(?:StartedError|FinishedError)|Mi(?:splaced(?:XMLDeclarationError|CDATAEndStringError)|xedContentDeclNot(?:StartedError|FinishedError))|S(?:t(?:andaloneValueError|ringNot(?:StartedError|ClosedError))|paceRequiredError|eparatorRequiredError)|N(?:MTOKENRequiredError|o(?:t(?:ationNot(?:StartedError|FinishedError)|WellBalancedError)|DTDError)|amespaceDeclarationError|AMERequiredError)|C(?:haracterRef(?:In(?:DTDError|PrologError|EpilogError)|AtEOFError)|o(?:nditionalSectionNot(?:StartedError|FinishedError)|mment(?:NotFinishedError|ContainsDoubleHyphenError))|DATANotFinishedError)|TagNameMismatchError|In(?:ternalError|valid(?:HexCharacterRefError|C(?:haracter(?:RefError|InEntityError|Error)|onditionalSectionError)|DecimalCharacterRefError|URIError|Encoding(?:NameError|Error)))|OutOfMemoryError|D(?:ocumentStartError|elegateAbortedParseError|OCTYPEDeclNotFinishedError)|U(?:RI(?:RequiredError|FragmentError)|n(?:declaredEntityError|parsedEntityError|knownEncodingError|finishedTagError))|P(?:CDATARequiredError|ublicIdentifierRequiredError|arsedEntityRef(?:MissingSemiError|NoNameError|In(?:Internal(?:SubsetError|Error)|PrologError|EpilogError)|AtEOFError)|r(?:ocessingInstructionNot(?:StartedError|FinishedError)|ematureDocumentEndError))|E(?:n(?:codingNotSupportedError|tity(?:Ref(?:In(?:DTDError|PrologError|EpilogError)|erence(?:MissingSemiError|WithoutNameError)|LoopError|AtEOFError)|BoundaryError|Not(?:StartedError|FinishedError)|Is(?:ParameterError|ExternalError)|ValueRequiredError))|qualExpectedError|lementContentDeclNot(?:StartedError|FinishedError)|xt(?:ernalS(?:tandaloneEntityError|ubsetNotFinishedError)|raContentError)|mptyDocumentError)|L(?:iteralNot(?:StartedError|FinishedError)|T(?:RequiredError|SlashRequiredError)|essThanSymbolInAttributeError)|Attribute(?:RedefinedError|HasNoValueError|Not(?:StartedError|FinishedError)|ListNot(?:StartedError|FinishedError)))|rocessingInstructionKind)|E(?:ntity(?:GeneralKind|DeclarationKind|UnparsedKind|P(?:ar(?:sedKind|ameterKind)|redefined))|lement(?:Declaration(?:MixedKind|UndefinedKind|E(?:lementKind|mptyKind)|Kind|AnyKind)|Kind))|Attribute(?:N(?:MToken(?:sKind|Kind)|otationKind)|CDATAKind|ID(?:Ref(?:sKind|Kind)|Kind)|DeclarationKind|En(?:tit(?:yKind|iesKind)|umerationKind)|Kind))|M(?:i(?:n(?:XEdge|iaturizableWindowMask|YEdge|uteCalendarUnit)|terLineJoinStyle|ddleSubelement|xedState)|o(?:nthCalendarUnit|deSwitchFunctionKey|use(?:Moved(?:Mask)?|E(?:ntered(?:Mask)?|ventSubtype|xited(?:Mask)?))|veToBezierPathElement|mentary(?:ChangeButton|Push(?:Button|InButton)|Light(?:Button)?))|enuFunctionKey|a(?:c(?:intoshInterfaceStyle|OSRomanStringEncoding)|tchesPredicateOperatorType|ppedRead|x(?:XEdge|YEdge))|ACHOperatingSystem)|B(?:MPFileType|o(?:ttomTabsBezelBorder|ldFontMask|rderlessWindowMask|x(?:Se(?:condary|parator)|OldStyle|Primary))|uttLineCapStyle|e(?:zelBorder|velLineJoinStyle|low(?:Bottom|Top)|gin(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|lueControlTint|ack(?:spaceCharacter|tabTextMovement|ingStore(?:Retained|Buffered|Nonretained)|TabCharacter|wardsSearch|groundTab)|r(?:owser(?:NoColumnResizing|UserColumnResizing|AutoColumnResizing)|eakFunctionKey))|S(?:h(?:ift(?:JISStringEncoding|KeyMask)|ow(?:ControlGlyphs|InvisibleGlyphs)|adowlessSquareBezelStyle)|y(?:s(?:ReqFunctionKey|tem(?:D(?:omainMask|efined(?:Mask)?)|FunctionKey))|mbolStringEncoding)|c(?:a(?:nnedOption|le(?:None|ToFit|Proportionally))|r(?:oll(?:er(?:NoPart|Increment(?:Page|Line|Arrow)|Decrement(?:Page|Line|Arrow)|Knob(?:Slot)?|Arrows(?:M(?:inEnd|axEnd)|None|DefaultSetting))|Wheel(?:Mask)?|LockFunctionKey)|eenChangedEventType))|t(?:opFunctionKey|r(?:ingDrawing(?:OneShot|DisableScreenFontSubstitution|Uses(?:DeviceMetrics|FontLeading|LineFragmentOrigin))|eam(?:Status(?:Reading|NotOpen|Closed|Open(?:ing)?|Error|Writing|AtEnd)|Event(?:Has(?:BytesAvailable|SpaceAvailable)|None|OpenCompleted|E(?:ndEncountered|rrorOccurred)))))|i(?:ngle(?:DateMode|UnderlineStyle)|ze(?:DownFontAction|UpFontAction))|olarisOperatingSystem|unOSOperatingSystem|pecialPageOrder|e(?:condCalendarUnit|lect(?:By(?:Character|Paragraph|Word)|i(?:ng(?:Next|Previous)|onAffinity(?:Downstream|Upstream))|edTab|FunctionKey)|gmentSwitchTracking(?:Momentary|Select(?:One|Any)))|quareLineCapStyle|witchButton|ave(?:ToOperation|Op(?:tions(?:Yes|No|Ask)|eration)|AsOperation)|mall(?:SquareBezelStyle|C(?:ontrolSize|apsFontMask)|IconButtonBezelStyle))|H(?:ighlightModeMatrix|SBModeColorPanel|o(?:ur(?:Minute(?:SecondDatePickerElementFlag|DatePickerElementFlag)|CalendarUnit)|rizontalRuler|meFunctionKey)|TTPCookieAcceptPolicy(?:Never|OnlyFromMainDocumentDomain|Always)|e(?:lp(?:ButtonBezelStyle|KeyMask|FunctionKey)|avierFontAction)|PUXOperatingSystem)|Year(?:MonthDa(?:yDatePickerElementFlag|tePickerElementFlag)|CalendarUnit)|N(?:o(?:n(?:StandardCharacterSetFontMask|ZeroWindingRule|activatingPanelMask|LossyASCIIStringEncoding)|Border|t(?:ification(?:SuspensionBehavior(?:Hold|Coalesce|D(?:eliverImmediately|rop))|NoCoalescing|CoalescingOn(?:Sender|Name)|DeliverImmediately|PostToAllSessions)|PredicateType|EqualToPredicateOperatorType)|S(?:cr(?:iptError|ollerParts)|ubelement|pecifierError)|CellMask|T(?:itle|opLevelContainersSpecifierError|abs(?:BezelBorder|NoBorder|LineBorder))|I(?:nterfaceStyle|mage)|UnderlineStyle|FontChangeAction)|u(?:ll(?:Glyph|CellType)|m(?:eric(?:Search|PadKeyMask)|berFormatter(?:Round(?:Half(?:Down|Up|Even)|Ceiling|Down|Up|Floor)|Behavior(?:10|Default)|S(?:cientificStyle|pellOutStyle)|NoStyle|CurrencyStyle|DecimalStyle|P(?:ercentStyle|ad(?:Before(?:Suffix|Prefix)|After(?:Suffix|Prefix))))))|e(?:t(?:Service(?:BadArgumentError|NotFoundError|C(?:ollisionError|ancelledError)|TimeoutError|InvalidError|UnknownError|ActivityInProgress)|workDomainMask)|wlineCharacter|xt(?:StepInterfaceStyle|FunctionKey))|EXTSTEPStringEncoding|a(?:t(?:iveShortGlyphPacking|uralTextAlignment)|rrowFontMask))|C(?:hange(?:ReadOtherContents|GrayCell(?:Mask)?|BackgroundCell(?:Mask)?|Cleared|Done|Undone|Autosaved)|MYK(?:ModeColorPanel|ColorSpaceModel)|ircular(?:BezelStyle|Slider)|o(?:n(?:stantValueExpressionType|t(?:inuousCapacityLevelIndicatorStyle|entsCellMask|ain(?:sComparison|erSpecifierError)|rol(?:Glyph|KeyMask))|densedFontMask)|lor(?:Panel(?:RGBModeMask|GrayModeMask|HSBModeMask|C(?:MYKModeMask|olorListModeMask|ustomPaletteModeMask|rayonModeMask)|WheelModeMask|AllModesMask)|ListModeColorPanel)|reServiceDirectory|m(?:p(?:osite(?:XOR|Source(?:In|O(?:ut|ver)|Atop)|Highlight|C(?:opy|lear)|Destination(?:In|O(?:ut|ver)|Atop)|Plus(?:Darker|Lighter))|ressedFontMask)|mandKeyMask))|u(?:stom(?:SelectorPredicateOperatorType|PaletteModeColorPanel)|r(?:sor(?:Update(?:Mask)?|PointingDevice)|veToBezierPathElement))|e(?:nterT(?:extAlignment|abStopType)|ll(?:State|H(?:ighlighted|as(?:Image(?:Horizontal|OnLeftOrBottom)|OverlappingImage))|ChangesContents|Is(?:Bordered|InsetButton)|Disabled|Editable|LightsBy(?:Gray|Background|Contents)|AllowsMixedState))|l(?:ipPagination|o(?:s(?:ePathBezierPathElement|ableWindowMask)|ckAndCalendarDatePickerStyle)|ear(?:ControlTint|DisplayFunctionKey|LineFunctionKey))|a(?:seInsensitive(?:Search|PredicateOption)|n(?:notCreateScriptCommandError|cel(?:Button|TextMovement))|chesDirectory|lculation(?:NoError|Overflow|DivideByZero|Underflow|LossOfPrecision)|rriageReturnCharacter)|r(?:itical(?:Request|AlertStyle)|ayonModeColorPanel))|T(?:hick(?:SquareBezelStyle|erSquareBezelStyle)|ypesetter(?:Behavior|HorizontalTabAction|ContainerBreakAction|ZeroAdvancementAction|OriginalBehavior|ParagraphBreakAction|WhitespaceAction|L(?:ineBreakAction|atestBehavior))|i(?:ckMark(?:Right|Below|Left|Above)|tledWindowMask|meZoneDatePickerElementFlag)|o(?:olbarItemVisibilityPriority(?:Standard|High|User|Low)|pTabsBezelBorder|ggleButton)|IFF(?:Compression(?:N(?:one|EXT)|CCITTFAX(?:3|4)|OldJPEG|JPEG|PackBits|LZW)|FileType)|e(?:rminate(?:Now|Cancel|Later)|xt(?:Read(?:InapplicableDocumentTypeError|WriteErrorM(?:inimum|aximum))|Block(?:M(?:i(?:nimum(?:Height|Width)|ddleAlignment)|a(?:rgin|ximum(?:Height|Width)))|B(?:o(?:ttomAlignment|rder)|aselineAlignment)|Height|TopAlignment|P(?:ercentageValueType|adding)|Width|AbsoluteValueType)|StorageEdited(?:Characters|Attributes)|CellType|ured(?:RoundedBezelStyle|BackgroundWindowMask|SquareBezelStyle)|Table(?:FixedLayoutAlgorithm|AutomaticLayoutAlgorithm)|Field(?:RoundedBezel|SquareBezel|AndStepperDatePickerStyle)|WriteInapplicableDocumentTypeError|ListPrependEnclosingMarker))|woByteGlyphPacking|ab(?:Character|TextMovement|le(?:tP(?:oint(?:Mask|EventSubtype)?|roximity(?:Mask|EventSubtype)?)|Column(?:NoResizing|UserResizingMask|AutoresizingMask)|View(?:ReverseSequentialColumnAutoresizingStyle|GridNone|S(?:olid(?:HorizontalGridLineMask|VerticalGridLineMask)|equentialColumnAutoresizingStyle)|NoColumnAutoresizing|UniformColumnAutoresizingStyle|FirstColumnOnlyAutoresizingStyle|LastColumnOnlyAutoresizingStyle)))|rackModeMatrix)|I(?:n(?:sert(?:CharFunctionKey|FunctionKey|LineFunctionKey)|t(?:Type|ernalS(?:criptError|pecifierError))|dexSubelement|validIndexSpecifierError|formational(?:Request|AlertStyle)|PredicateOperatorType)|talicFontMask|SO(?:2022JPStringEncoding|Latin(?:1StringEncoding|2StringEncoding))|dentityMappingCharacterCollection|llegalTextMovement|mage(?:R(?:ight|ep(?:MatchesDevice|LoadStatus(?:ReadingHeader|Completed|InvalidData|Un(?:expectedEOF|knownType)|WillNeedAllData)))|Below|C(?:ellType|ache(?:BySize|Never|Default|Always))|Interpolation(?:High|None|Default|Low)|O(?:nly|verlaps)|Frame(?:Gr(?:oove|ayBezel)|Button|None|Photo)|L(?:oadStatus(?:ReadError|C(?:ompleted|ancelled)|InvalidData|UnexpectedEOF)|eft)|A(?:lign(?:Right|Bottom(?:Right|Left)?|Center|Top(?:Right|Left)?|Left)|bove)))|O(?:n(?:State|eByteGlyphPacking|OffButton|lyScrollerArrows)|ther(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|TextMovement)|SF1OperatingSystem|pe(?:n(?:GL(?:GO(?:Re(?:setLibrary|tainRenderers)|ClearFormatCache|FormatCacheSize)|PFA(?:R(?:obust|endererID)|M(?:inimumPolicy|ulti(?:sample|Screen)|PSafe|aximumPolicy)|BackingStore|S(?:creenMask|te(?:ncilSize|reo)|ingleRenderer|upersample|ample(?:s|Buffers|Alpha))|NoRecovery|C(?:o(?:lor(?:Size|Float)|mpliant)|losestPolicy)|OffScreen|D(?:oubleBuffer|epthSize)|PixelBuffer|VirtualScreenCount|FullScreen|Window|A(?:cc(?:umSize|elerated)|ux(?:Buffers|DepthStencil)|l(?:phaSize|lRenderers))))|StepUnicodeReservedBase)|rationNotSupportedForKeyS(?:criptError|pecifierError))|ffState|KButton|rPredicateType|bjC(?:B(?:itfield|oolType)|S(?:hortType|tr(?:ingType|uctType)|electorType)|NoType|CharType|ObjectType|DoubleType|UnionType|PointerType|VoidType|FloatType|Long(?:Type|longType)|ArrayType))|D(?:i(?:s(?:c(?:losureBezelStyle|reteCapacityLevelIndicatorStyle)|playWindowRunLoopOrdering)|acriticInsensitivePredicateOption|rect(?:Selection|PredicateModifier))|o(?:c(?:ModalWindowMask|ument(?:Directory|ationDirectory))|ubleType|wn(?:TextMovement|ArrowFunctionKey))|e(?:s(?:cendingPageOrder|ktopDirectory)|cimalTabStopType|v(?:ice(?:NColorSpaceModel|IndependentModifierFlagsMask)|eloper(?:Directory|ApplicationDirectory))|fault(?:ControlTint|TokenStyle)|lete(?:Char(?:acter|FunctionKey)|FunctionKey|LineFunctionKey)|moApplicationDirectory)|a(?:yCalendarUnit|teFormatter(?:MediumStyle|Behavior(?:10|Default)|ShortStyle|NoStyle|FullStyle|LongStyle))|ra(?:wer(?:Clos(?:ingState|edState)|Open(?:ingState|State))|gOperation(?:Generic|Move|None|Copy|Delete|Private|Every|Link|All)))|U(?:ser(?:CancelledError|D(?:irectory|omainMask)|FunctionKey)|RL(?:Handle(?:NotLoaded|Load(?:Succeeded|InProgress|Failed))|CredentialPersistence(?:None|Permanent|ForSession))|n(?:scaledWindowMask|cachedRead|i(?:codeStringEncoding|talicFontMask|fiedTitleAndToolbarWindowMask)|d(?:o(?:CloseGroupingRunLoopOrdering|FunctionKey)|e(?:finedDateComponent|rline(?:Style(?:Single|None|Thick|Double)|Pattern(?:Solid|D(?:ot|ash(?:Dot(?:Dot)?)?)))))|known(?:ColorSpaceModel|P(?:ointingDevice|ageOrder)|KeyS(?:criptError|pecifierError))|boldFontMask)|tilityWindowMask|TF8StringEncoding|p(?:dateWindowsRunLoopOrdering|TextMovement|ArrowFunctionKey))|J(?:ustifiedTextAlignment|PEG(?:2000FileType|FileType)|apaneseEUC(?:GlyphPacking|StringEncoding))|P(?:o(?:s(?:t(?:Now|erFontMask|WhenIdle|ASAP)|iti(?:on(?:Replace|Be(?:fore|ginning)|End|After)|ve(?:IntType|DoubleType|FloatType)))|pUp(?:NoArrow|ArrowAt(?:Bottom|Center))|werOffEventType|rtraitOrientation)|NGFileType|ush(?:InCell(?:Mask)?|OnPushOffButton)|e(?:n(?:TipMask|UpperSideMask|PointingDevice|LowerSideMask)|riodic(?:Mask)?)|P(?:S(?:caleField|tatus(?:Title|Field)|aveButton)|N(?:ote(?:Title|Field)|ame(?:Title|Field))|CopiesField|TitleField|ImageButton|OptionsButton|P(?:a(?:perFeedButton|ge(?:Range(?:To|From)|ChoiceMatrix))|reviewButton)|LayoutButton)|lainTextTokenStyle|a(?:useFunctionKey|ragraphSeparatorCharacter|ge(?:DownFunctionKey|UpFunctionKey))|r(?:int(?:ing(?:ReplyLater|Success|Cancelled|Failure)|ScreenFunctionKey|erTable(?:NotFound|OK|Error)|FunctionKey)|o(?:p(?:ertyList(?:XMLFormat|MutableContainers(?:AndLeaves)?|BinaryFormat|Immutable|OpenStepFormat)|rietaryStringEncoding)|gressIndicator(?:BarStyle|SpinningStyle|Preferred(?:SmallThickness|Thickness|LargeThickness|AquaThickness)))|e(?:ssedTab|vFunctionKey))|L(?:HeightForm|CancelButton|TitleField|ImageButton|O(?:KButton|rientationMatrix)|UnitsButton|PaperNameButton|WidthForm))|E(?:n(?:terCharacter|d(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|v(?:e(?:nOddWindingRule|rySubelement)|aluatedObjectExpressionType)|qualTo(?:Comparison|PredicateOperatorType)|ra(?:serPointingDevice|CalendarUnit|DatePickerElementFlag)|x(?:clude(?:10|QuickDrawElementsIconCreationOption)|pandedFontMask|ecuteFunctionKey))|V(?:i(?:ew(?:M(?:in(?:XMargin|YMargin)|ax(?:XMargin|YMargin))|HeightSizable|NotSizable|WidthSizable)|aPanelFontAction)|erticalRuler|a(?:lidationErrorM(?:inimum|aximum)|riableExpressionType))|Key(?:SpecifierEvaluationScriptError|Down(?:Mask)?|Up(?:Mask)?|PathExpressionType|Value(?:MinusSetMutation|SetSetMutation|Change(?:Re(?:placement|moval)|Setting|Insertion)|IntersectSetMutation|ObservingOption(?:New|Old)|UnionSetMutation|ValidationError))|QTMovie(?:NormalPlayback|Looping(?:BackAndForthPlayback|Playback))|F(?:1(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|7FunctionKey|i(?:nd(?:PanelAction(?:Replace(?:A(?:ndFind|ll(?:InSelection)?))?|S(?:howFindPanel|e(?:tFindString|lectAll(?:InSelection)?))|Next|Previous)|FunctionKey)|tPagination|le(?:Read(?:No(?:SuchFileError|PermissionError)|CorruptFileError|In(?:validFileNameError|applicableStringEncodingError)|Un(?:supportedSchemeError|knownError))|HandlingPanel(?:CancelButton|OKButton)|NoSuchFileError|ErrorM(?:inimum|aximum)|Write(?:NoPermissionError|In(?:validFileNameError|applicableStringEncodingError)|OutOfSpaceError|Un(?:supportedSchemeError|knownError))|LockingError)|xedPitchFontMask)|2(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|o(?:nt(?:Mo(?:noSpaceTrait|dernSerifsClass)|BoldTrait|S(?:ymbolicClass|criptsClass|labSerifsClass|ansSerifClass)|C(?:o(?:ndensedTrait|llectionApplicationOnlyMask)|larendonSerifsClass)|TransitionalSerifsClass|I(?:ntegerAdvancementsRenderingMode|talicTrait)|O(?:ldStyleSerifsClass|rnamentalsClass)|DefaultRenderingMode|U(?:nknownClass|IOptimizedTrait)|Panel(?:S(?:hadowEffectModeMask|t(?:andardModesMask|rikethroughEffectModeMask)|izeModeMask)|CollectionModeMask|TextColorEffectModeMask|DocumentColorEffectModeMask|UnderlineEffectModeMask|FaceModeMask|All(?:ModesMask|EffectsModeMask))|ExpandedTrait|VerticalTrait|F(?:amilyClassMask|reeformSerifsClass)|Antialiased(?:RenderingMode|IntegerAdvancementsRenderingMode))|cusRing(?:Below|Type(?:None|Default|Exterior)|Only|Above)|urByteGlyphPacking|rm(?:attingError(?:M(?:inimum|aximum))?|FeedCharacter))|8FunctionKey|unction(?:ExpressionType|KeyMask)|3(?:1FunctionKey|2FunctionKey|3FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey)|9FunctionKey|4FunctionKey|P(?:RevertButton|S(?:ize(?:Title|Field)|etButton)|CurrentField|Preview(?:Button|Field))|l(?:oat(?:ingPointSamplesBitmapFormat|Type)|agsChanged(?:Mask)?)|axButton|5FunctionKey|6FunctionKey)|W(?:heelModeColorPanel|indow(?:s(?:NTOperatingSystem|CP125(?:1StringEncoding|2StringEncoding|3StringEncoding|4StringEncoding|0StringEncoding)|95(?:InterfaceStyle|OperatingSystem))|M(?:iniaturizeButton|ovedEventType)|Below|CloseButton|ToolbarButton|ZoomButton|Out|DocumentIconButton|ExposedEventType|Above)|orkspaceLaunch(?:NewInstance|InhibitingBackgroundOnly|Default|PreferringClassic|WithoutA(?:ctivation|ddingToRecents)|A(?:sync|nd(?:Hide(?:Others)?|Print)|llowingClassicStartup))|eek(?:day(?:CalendarUnit|OrdinalCalendarUnit)|CalendarUnit)|a(?:ntsBidiLevels|rningAlertStyle)|r(?:itingDirection(?:RightToLeft|Natural|LeftToRight)|apCalendarComponents))|L(?:i(?:stModeMatrix|ne(?:Moves(?:Right|Down|Up|Left)|B(?:order|reakBy(?:C(?:harWrapping|lipping)|Truncating(?:Middle|Head|Tail)|WordWrapping))|S(?:eparatorCharacter|weep(?:Right|Down|Up|Left))|ToBezierPathElement|DoesntMove|arSlider)|teralSearch|kePredicateOperatorType|ghterFontAction|braryDirectory)|ocalDomainMask|e(?:ssThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|ft(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey))|a(?:yout(?:RightToLeft|NotDone|CantFit|OutOfGlyphs|Done|LeftToRight)|ndscapeOrientation)|ABColorSpaceModel)|A(?:sc(?:iiWithDoubleByteEUCGlyphPacking|endingPageOrder)|n(?:y(?:Type|PredicateModifier|EventMask)|choredSearch|imation(?:Blocking|Nonblocking(?:Threaded)?|E(?:ffect(?:DisappearingItemDefault|Poof)|ase(?:In(?:Out)?|Out))|Linear)|dPredicateType)|t(?:Bottom|tachmentCharacter|omicWrite|Top)|SCIIStringEncoding|d(?:obe(?:GB1CharacterCollection|CNS1CharacterCollection|Japan(?:1CharacterCollection|2CharacterCollection)|Korea1CharacterCollection)|dTraitFontAction|minApplicationDirectory)|uto(?:saveOperation|Pagination)|pp(?:lication(?:SupportDirectory|D(?:irectory|e(?:fined(?:Mask)?|legateReply(?:Success|Cancel|Failure)|activatedEventType))|ActivatedEventType)|KitDefined(?:Mask)?)|l(?:ternateKeyMask|pha(?:ShiftKeyMask|NonpremultipliedBitmapFormat|FirstBitmapFormat)|ert(?:SecondButtonReturn|ThirdButtonReturn|OtherReturn|DefaultReturn|ErrorReturn|FirstButtonReturn|AlternateReturn)|l(?:ScrollerParts|DomainsMask|PredicateModifier|LibrariesDirectory|ApplicationsDirectory))|rgument(?:sWrongScriptError|EvaluationScriptError)|bove(?:Bottom|Top)|WTEventType)))(?:\\b)"},{token:"support.function.C99.c",regex:s.cFunctions},{token:n.getKeywords(),regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"punctuation.section.scope.begin.objc",regex:"\\[",next:"bracketed_content"},{token:"meta.function.objc",regex:"^(?:-|\\+)\\s*"}],constant_NSString:[{token:"constant.character.escape.objc",regex:e},{token:"invalid.illegal.unknown-escape.objc",regex:"\\\\."},{token:"string",regex:'[^"\\\\]+'},{token:"punctuation.definition.string.end",regex:'"',next:"start"}],protocol_list:[{token:"punctuation.section.scope.end.objc",regex:">",next:"start"},{token:"support.other.protocol.objc",regex:"\bNS(?:GlyphStorage|M(?:utableCopying|enuItem)|C(?:hangeSpelling|o(?:ding|pying|lorPicking(?:Custom|Default)))|T(?:oolbarItemValidations|ext(?:Input|AttachmentCell))|I(?:nputServ(?:iceProvider|erMouseTracker)|gnoreMisspelledWords)|Obj(?:CTypeSerializationCallBack|ect)|D(?:ecimalNumberBehaviors|raggingInfo)|U(?:serInterfaceValidations|RL(?:HandleClient|DownloadDelegate|ProtocolClient|AuthenticationChallengeSender))|Validated(?:ToobarItem|UserInterfaceItem)|Locking)\b"}],selectors:[{token:"support.function.any-method.name-of-parameter.objc",regex:"\\b(?:[a-zA-Z_:][\\w]*)+"},{token:"punctuation",regex:"\\)",next:"start"}],bracketed_content:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:["support.function.any-method.objc"],regex:"(?:predicateWithFormat:| NSPredicate predicateWithFormat:)",next:"start"},{token:"support.function.any-method.objc",regex:"\\w+(?::|(?=]))",next:"start"}],bracketed_strings:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:"keyword.operator.logical.predicate.cocoa",regex:"\\b(?:AND|OR|NOT|IN)\\b"},{token:["invalid.illegal.unknown-method.objc","punctuation.separator.arguments.objc"],regex:"\\b(\\w+)(:)"},{regex:"\\b(?:ALL|ANY|SOME|NONE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:NULL|NIL|SELF|TRUE|YES|FALSE|NO|FIRST|LAST|SIZE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:MATCHES|CONTAINS|BEGINSWITH|ENDSWITH|BETWEEN)\\b",token:"keyword.operator.comparison.predicate.cocoa"},{regex:"\\bC(?:ASEINSENSITIVE|I)\\b",token:"keyword.other.modifier.predicate.cocoa"},{regex:"\\b(?:ANYKEY|SUBQUERY|CAST|TRUEPREDICATE|FALSEPREDICATE)\\b",token:"keyword.other.predicate.cocoa"},{regex:e,token:"constant.character.escape.objc"},{regex:"\\\\.",token:"invalid.illegal.unknown-escape.objc"},{token:"string",regex:'[^"\\\\]'},{token:"punctuation.definition.string.end.objc",regex:'"',next:"predicates"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],methods:[{token:"meta.function.objc",regex:"(?=\\{|#)|;",next:"start"}]};for(var u in r)this.$rules[u]?this.$rules[u].push&&this.$rules[u].push.apply(this.$rules[u],r[u]):this.$rules[u]=r[u];this.$rules.bracketed_content=this.$rules.bracketed_content.concat(this.$rules.start,t),this.embedRules(i,"doc-",[i.getEndRule("start")])};r.inherits(u,o),t.ObjectiveCHighlightRules=u}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/objectivec",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/objectivec_highlight_rules","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./objectivec_highlight_rules").ObjectiveCHighlightRules,o=e("./folding/cstyle").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$id="ace/mode/objectivec"}.call(u.prototype),t.Mode=u}) diff --git a/public/themes/pterodactyl/vendor/adminlte/admin.min.css b/public/themes/pterodactyl/vendor/adminlte/admin.min.css index 20792ca50..5df836823 100755 --- a/public/themes/pterodactyl/vendor/adminlte/admin.min.css +++ b/public/themes/pterodactyl/vendor/adminlte/admin.min.css @@ -1,7 +1,7 @@ -@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);/*! - * AdminLTE v2.3.8 +/*! + * AdminLTE v2.4.0 * Author: Almsaeed Studio - * Website: Almsaeed Studio + * Website: Almsaeed Studio * License: Open source - MIT * Please visit http://opensource.org/licenses/MIT for more information -!*/html,body{height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{height:100%;position:relative;overflow-x:hidden;overflow-y:auto}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed}.content-wrapper,.right-side,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .right-side,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.right-side,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .right-side,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .right-side,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper,.right-side{min-height:100%;background-color:#ecf0f5;z-index:800}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}body.hold-transition .content-wrapper,body.hold-transition .right-side,body.hold-transition .main-footer,body.hold-transition .main-sidebar,body.hold-transition .left-side,body.hold-transition .main-header .navbar,body.hold-transition .main-header .logo{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#3c8dbc}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#72afd2}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header .navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header .navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:"\f0c9"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-mini{display:none}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar,.left-side{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar,.left-side{padding-top:100px}}@media (max-width:767px){.main-sidebar,.left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar,.sidebar-collapse .left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar,.sidebar-open .left-side{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-right:5px}.sidebar-menu>li .badge{margin-top:3px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left,.sidebar-menu li>a>.pull-right-container>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px}.sidebar-menu li>a>.fa-angle-left{position:absolute;top:50%;right:10px;margin-top:-8px}.sidebar-menu li.active>a>.fa-angle-left,.sidebar-menu li.active>a>.pull-right-container>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu li.active>.treeview-menu{display:block}.sidebar-menu .treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.sidebar-menu .treeview-menu .treeview-menu{padding-left:20px}.sidebar-menu .treeview-menu>li{margin:0}.sidebar-menu .treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.sidebar-menu .treeview-menu>li>a>.fa,.sidebar-menu .treeview-menu>li>a>.glyphicon,.sidebar-menu .treeview-menu>li>a>.ion{width:20px}.sidebar-menu .treeview-menu>li>a>.pull-right-container>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.pull-right-container>.fa-angle-down,.sidebar-menu .treeview-menu>li>a>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.fa-angle-down{width:auto}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container{position:relative!important;float:right;width:auto!important;left:180px !important;top:-22px !important;z-index:900}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container>.label:not(:first-of-type){display:none}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right-container{position:absolute;right:10px;top:50%;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#b8c7ce}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#d2d6de}.form-control:focus{border-color:#3c8dbc;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control,.form-group.has-success .input-group-addon{border-color:#00a65a;box-shadow:none}.form-group.has-success .help-block{color:#00a65a}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control,.form-group.has-warning .input-group-addon{border-color:#f39c12;box-shadow:none}.form-group.has-warning .help-block{color:#f39c12}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control,.form-group.has-error .input-group-addon{border-color:#dd4b39;box-shadow:none}.form-group.has-error .help-block{color:#dd4b39}.input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#3c8dbc}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#d2d6de}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #d2d6de}.box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #3c8dbc}.box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{position:absolute;right:10px;top:5px}.box-header>.box-tools [data-toggle="tooltip"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.box-header>.box-tools .dropdown-menu>li>a{color:#444!important}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#d2d6de !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#3c8dbc}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#3c8dbc;border-color:#367fa9}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none !important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef}.nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39}.nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12}.nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a}.nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#d2d6de}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#d2d6de;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#d2d6de}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{background:#d2d6de}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#3c8dbc}.select2-dropdown{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none;border:1px solid #3c8dbc}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#d2d6de !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#0073b7 !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001f3f !important}.bg-teal{background-color:#39cccc !important}.bg-olive{background-color:#3d9970 !important}.bg-lime{background-color:#01ff70 !important}.bg-orange{background-color:#ff851b !important}.bg-fuchsia{background-color:#f012be !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#d81b60 !important}.bg-gray-active{color:#000;background-color:#b5bbc8 !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#005384 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#0073b7 !important}.text-black{color:#111 !important}.text-light-blue{color:#3c8dbc !important}.text-green{color:#00a65a !important}.text-gray{color:#d2d6de !important}.text-navy{color:#001f3f !important}.text-teal{color:#39cccc !important}.text-olive{color:#3d9970 !important}.text-lime{color:#01ff70 !important}.text-orange{color:#ff851b !important}.text-fuchsia{color:#f012be !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#d81b60 !important}.link-muted{color:#7a869d}.link-muted:hover,.link-muted:focus{color:#606c84}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none !important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px !important;width:auto !important;height:auto !important}.bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static !important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #d2d6de;padding:3px}.img-bordered-sm{border:2px solid #d2d6de;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100% !important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}} \ No newline at end of file + */html,body{height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{height:100%;position:relative;overflow-x:hidden;overflow-y:auto}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed}.content-wrapper,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper{min-height:100%;background-color:#ecf0f5;z-index:800}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}.fixed .wrapper{overflow:hidden}.hold-transition .content-wrapper,.hold-transition .right-side,.hold-transition .main-footer,.hold-transition .main-sidebar,.hold-transition .left-side,.hold-transition .main-header .navbar,.hold-transition .main-header .logo,.hold-transition .menu-open .fa-angle-left{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#337ab7}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#23527c}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header .navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header .navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:"\f0c9"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-mini{display:none}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar{padding-top:100px}}@media (max-width:767px){.main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-right:5px}.sidebar-menu>li .badge{margin-top:3px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left,.sidebar-menu li>a>.pull-right-container>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px;-webkit-transition:transform .5s ease;-o-transition:transform .5s ease;transition:transform .5s ease}.sidebar-menu li>a>.fa-angle-left{position:absolute;top:50%;right:10px;margin-top:-8px}.sidebar-menu .menu-open>a>.fa-angle-left,.sidebar-menu .menu-open>a>.pull-right-container>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu .active>.treeview-menu{display:block}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container{position:relative !important;float:right;width:auto !important;left:180px !important;top:-22px !important;z-index:900}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container>.label:not(:first-of-type){display:none}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-expanded-on-hover .main-footer,.sidebar-expanded-on-hover .content-wrapper{margin-left:50px}.sidebar-expanded-on-hover .main-sidebar{box-shadow:3px 0 8px rgba(0,0,0,0.125)}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right-container{position:absolute;right:10px;top:50%;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.fixed .control-sidebar{position:fixed;height:100%;overflow-y:auto;padding-bottom:50px}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#b8c7ce}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#d2d6de}.form-control:focus{border-color:#3c8dbc;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control,.form-group.has-success .input-group-addon{border-color:#00a65a;box-shadow:none}.form-group.has-success .help-block{color:#00a65a}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control,.form-group.has-warning .input-group-addon{border-color:#f39c12;box-shadow:none}.form-group.has-warning .help-block{color:#f39c12}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control,.form-group.has-error .input-group-addon{border-color:#dd4b39;box-shadow:none}.form-group.has-error .help-block{color:#dd4b39}.input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#3c8dbc}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#d2d6de}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #d2d6de}.box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #3c8dbc}.box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{position:absolute;right:10px;top:5px}.box-header>.box-tools [data-toggle="tooltip"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.box-header>.box-tools .dropdown-menu>li>a{color:#444!important}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#d2d6de !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#3c8dbc}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#3c8dbc;border-color:#367fa9}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li.disabled>a{color:#777}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none !important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef}.nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39}.nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12}.nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a}.nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#d2d6de}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#d2d6de;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.treeview-menu .treeview-menu{padding-left:20px}.treeview-menu>li{margin:0}.treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.treeview-menu>li>a>.fa,.treeview-menu>li>a>.glyphicon,.treeview-menu>li>a>.ion{width:20px}.treeview-menu>li>a>.pull-right-container>.fa-angle-left,.treeview-menu>li>a>.pull-right-container>.fa-angle-down,.treeview-menu>li>a>.fa-angle-left,.treeview-menu>li>a>.fa-angle-down{width:auto}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#d2d6de}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{background:#d2d6de}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#3c8dbc}.select2-dropdown{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none}.select2-container--default.select2-container--focus .select2-selection--multiple,.select2-container--default .select2-search--dropdown .select2-search__field{border-color:#3c8dbc !important}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.box .datepicker-inline,.box .datepicker-inline .datepicker-days,.box .datepicker-inline>table,.box .datepicker-inline .datepicker-days>table{width:100%}.box .datepicker-inline td:hover,.box .datepicker-inline .datepicker-days td:hover,.box .datepicker-inline>table td:hover,.box .datepicker-inline .datepicker-days>table td:hover{background-color:rgba(255,255,255,0.3)}.box .datepicker-inline td.day.old,.box .datepicker-inline .datepicker-days td.day.old,.box .datepicker-inline>table td.day.old,.box .datepicker-inline .datepicker-days>table td.day.old,.box .datepicker-inline td.day.new,.box .datepicker-inline .datepicker-days td.day.new,.box .datepicker-inline>table td.day.new,.box .datepicker-inline .datepicker-days>table td.day.new{color:#777}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#d2d6de !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#0073b7 !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001f3f !important}.bg-teal{background-color:#39cccc !important}.bg-olive{background-color:#3d9970 !important}.bg-lime{background-color:#01ff70 !important}.bg-orange{background-color:#ff851b !important}.bg-fuchsia{background-color:#f012be !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#d81b60 !important}.bg-gray-active{color:#000;background-color:#b5bbc8 !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#005384 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#0073b7 !important}.text-black{color:#111 !important}.text-light-blue{color:#3c8dbc !important}.text-green{color:#00a65a !important}.text-gray{color:#d2d6de !important}.text-navy{color:#001f3f !important}.text-teal{color:#39cccc !important}.text-olive{color:#3d9970 !important}.text-lime{color:#01ff70 !important}.text-orange{color:#ff851b !important}.text-fuchsia{color:#f012be !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#d81b60 !important}.link-muted{color:#7a869d}.link-muted:hover,.link-muted:focus{color:#606c84}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none !important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px !important;width:auto !important;height:auto !important}.bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static !important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #d2d6de;padding:3px}.img-bordered-sm{border:2px solid #d2d6de;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100% !important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}} \ No newline at end of file diff --git a/public/themes/pterodactyl/vendor/adminlte/adminlte.min.css.map b/public/themes/pterodactyl/vendor/adminlte/adminlte.min.css.map new file mode 100755 index 000000000..6ee069e22 --- /dev/null +++ b/public/themes/pterodactyl/vendor/adminlte/adminlte.min.css.map @@ -0,0 +1,140 @@ +{ + "version": 3, + "file": "adminlte.min.css", + "sources": [ + "../../build/scss/AdminLTE.scss", + "../../build/scss/_bootstrap_variables.scss", + "../../bower_components/bootstrap/scss/bootstrap.scss", + "../../bower_components/bootstrap/scss/_variables.scss", + "../../bower_components/bootstrap/scss/_mixins.scss", + "../../bower_components/bootstrap/scss/mixins/_breakpoints.scss", + "../../bower_components/bootstrap/scss/mixins/_hover.scss", + "../../bower_components/bootstrap/scss/mixins/_image.scss", + "../../bower_components/bootstrap/scss/mixins/_badge.scss", + "../../bower_components/bootstrap/scss/mixins/_resize.scss", + "../../bower_components/bootstrap/scss/mixins/_screen-reader.scss", + "../../bower_components/bootstrap/scss/mixins/_size.scss", + "../../bower_components/bootstrap/scss/mixins/_reset-text.scss", + "../../bower_components/bootstrap/scss/mixins/_text-emphasis.scss", + "../../bower_components/bootstrap/scss/mixins/_text-hide.scss", + "../../bower_components/bootstrap/scss/mixins/_text-truncate.scss", + "../../bower_components/bootstrap/scss/mixins/_transforms.scss", + "../../bower_components/bootstrap/scss/mixins/_visibility.scss", + "../../bower_components/bootstrap/scss/mixins/_alert.scss", + "../../bower_components/bootstrap/scss/mixins/_buttons.scss", + "../../bower_components/bootstrap/scss/mixins/_cards.scss", + "../../bower_components/bootstrap/scss/mixins/_pagination.scss", + "../../bower_components/bootstrap/scss/mixins/_lists.scss", + "../../bower_components/bootstrap/scss/mixins/_list-group.scss", + "../../bower_components/bootstrap/scss/mixins/_nav-divider.scss", + "../../bower_components/bootstrap/scss/mixins/_forms.scss", + "../../bower_components/bootstrap/scss/mixins/_table-row.scss", + "../../bower_components/bootstrap/scss/mixins/_background-variant.scss", + "../../bower_components/bootstrap/scss/mixins/_border-radius.scss", + "../../bower_components/bootstrap/scss/mixins/_gradients.scss", + "../../bower_components/bootstrap/scss/mixins/_clearfix.scss", + "../../bower_components/bootstrap/scss/mixins/_grid-framework.scss", + "../../bower_components/bootstrap/scss/mixins/_grid.scss", + "../../bower_components/bootstrap/scss/mixins/_float.scss", + "../../bower_components/bootstrap/scss/_custom.scss", + "../../bower_components/bootstrap/scss/_normalize.scss", + "../../bower_components/bootstrap/scss/_print.scss", + "../../bower_components/bootstrap/scss/_reboot.scss", + "../../bower_components/bootstrap/scss/_type.scss", + "../../bower_components/bootstrap/scss/_images.scss", + "../../bower_components/bootstrap/scss/_code.scss", + "../../bower_components/bootstrap/scss/_grid.scss", + "../../bower_components/bootstrap/scss/_tables.scss", + "../../bower_components/bootstrap/scss/_forms.scss", + "../../bower_components/bootstrap/scss/_buttons.scss", + "../../bower_components/bootstrap/scss/_transitions.scss", + "../../bower_components/bootstrap/scss/_dropdown.scss", + "../../bower_components/bootstrap/scss/_button-group.scss", + "../../bower_components/bootstrap/scss/_input-group.scss", + "../../bower_components/bootstrap/scss/_custom-forms.scss", + "../../bower_components/bootstrap/scss/_nav.scss", + "../../bower_components/bootstrap/scss/_navbar.scss", + "../../bower_components/bootstrap/scss/_card.scss", + "../../bower_components/bootstrap/scss/_breadcrumb.scss", + "../../bower_components/bootstrap/scss/_pagination.scss", + "../../bower_components/bootstrap/scss/_badge.scss", + "../../bower_components/bootstrap/scss/_jumbotron.scss", + "../../bower_components/bootstrap/scss/_alert.scss", + "../../bower_components/bootstrap/scss/_progress.scss", + "../../bower_components/bootstrap/scss/_media.scss", + "../../bower_components/bootstrap/scss/_list-group.scss", + "../../bower_components/bootstrap/scss/_responsive-embed.scss", + "../../bower_components/bootstrap/scss/_close.scss", + "../../bower_components/bootstrap/scss/_modal.scss", + "../../bower_components/bootstrap/scss/_tooltip.scss", + "../../bower_components/bootstrap/scss/_popover.scss", + "../../bower_components/bootstrap/scss/_carousel.scss", + "../../bower_components/bootstrap/scss/_utilities.scss", + "../../bower_components/bootstrap/scss/utilities/_align.scss", + "../../bower_components/bootstrap/scss/utilities/_background.scss", + "../../bower_components/bootstrap/scss/utilities/_borders.scss", + "../../bower_components/bootstrap/scss/utilities/_clearfix.scss", + "../../bower_components/bootstrap/scss/utilities/_display.scss", + "../../bower_components/bootstrap/scss/utilities/_flex.scss", + "../../bower_components/bootstrap/scss/utilities/_float.scss", + "../../bower_components/bootstrap/scss/utilities/_position.scss", + "../../bower_components/bootstrap/scss/utilities/_screenreaders.scss", + "../../bower_components/bootstrap/scss/utilities/_sizing.scss", + "../../bower_components/bootstrap/scss/utilities/_spacing.scss", + "../../bower_components/bootstrap/scss/utilities/_text.scss", + "../../bower_components/bootstrap/scss/utilities/_visibility.scss", + "../../build/scss/_variables.scss", + "../../build/scss/_mixins.scss", + "../../build/scss/_layout.scss", + "../../build/scss/_header.scss", + "../../build/scss/_sidebar.scss", + "../../build/scss/_sidebar-mini.scss", + "../../build/scss/_control-sidebar.scss", + "../../build/scss/_dropdown.scss", + "../../build/scss/_forms.scss", + "../../build/scss/_progress-bars.scss", + "../../build/scss/_small-box.scss", + "../../build/scss/_boxes.scss", + "../../build/scss/_info-box.scss", + "../../build/scss/_timeline.scss", + "../../build/scss/_buttons.scss", + "../../build/scss/_callout.scss", + "../../build/scss/_alerts.scss", + "../../build/scss/_navs.scss", + "../../build/scss/_products.scss", + "../../build/scss/_table.scss", + "../../build/scss/_labels.scss", + "../../build/scss/_direct-chat.scss", + "../../build/scss/_users-list.scss", + "../../build/scss/_site-search.scss", + "../../build/scss/_carousel.scss", + "../../build/scss/_modal.scss", + "../../build/scss/_social-widgets.scss", + "../../build/scss/_mailbox.scss", + "../../build/scss/_lockscreen.scss", + "../../build/scss/_login_and_register.scss", + "../../build/scss/_404_500_errors.scss", + "../../build/scss/_invoice.scss", + "../../build/scss/_profile.scss", + "../../build/scss/_bootstrap-social.scss", + "../../build/scss/_fullcalendar.scss", + "../../build/scss/_select2.scss", + "../../build/scss/_miscellaneous.scss", + "../../build/scss/_print.scss", + "../../build/scss/skins/_all-skins.scss", + "../../build/scss/skins/skin-blue.scss", + "../../build/scss/skins/skin-blue-light.scss", + "../../build/scss/skins/skin-black.scss", + "../../build/scss/skins/skin-black-light.scss", + "../../build/scss/skins/skin-green.scss", + "../../build/scss/skins/skin-green-light.scss", + "../../build/scss/skins/skin-red.scss", + "../../build/scss/skins/skin-red-light.scss", + "../../build/scss/skins/skin-yellow.scss", + "../../build/scss/skins/skin-yellow-light.scss", + "../../build/scss/skins/skin-purple.scss", + "../../build/scss/skins/skin-purple-light.scss" + ], + "mappings": "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", + "names": [] +} \ No newline at end of file diff --git a/public/themes/pterodactyl/vendor/adminlte/app.min.js b/public/themes/pterodactyl/vendor/adminlte/app.min.js index 7efb107e1..c9933e09f 100755 --- a/public/themes/pterodactyl/vendor/adminlte/app.min.js +++ b/public/themes/pterodactyl/vendor/adminlte/app.min.js @@ -1,13 +1,14 @@ /*! AdminLTE app.js - * ================ - * Main JS application file for AdminLTE v2. This file - * should be included in all pages. It controls some layout - * options and implements exclusive AdminLTE plugins. - * - * @Author Almsaeed Studio - * @Support - * @Email - * @version 2.3.8 - * @license MIT - */ -function _init(){"use strict";$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$("body, html, .wrapper").css("height","auto"),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){$(".layout-boxed > .wrapper").css("overflow","hidden");var a=$(".main-footer").outerHeight()||0,b=$(".main-header").outerHeight()+a,c=$(window).height(),d=$(".sidebar").height()||0;if($("body").hasClass("fixed"))$(".content-wrapper, .right-side").css("min-height",c-a);else{var e;c>=d?($(".content-wrapper, .right-side").css("min-height",c-b),e=c-b):($(".content-wrapper, .right-side").css("min-height",d),e=d);var f=$($.AdminLTE.options.controlSidebarOptions.selector);"undefined"!=typeof f&&f.height()>e&&$(".content-wrapper, .right-side").css("min-height",f.height())}},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimScroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu={activate:function(a){var b=$.AdminLTE.options.screenSizes;$(document).on("click",a,function(a){a.preventDefault(),$(window).width()>b.sm-1?$("body").hasClass("sidebar-collapse")?$("body").removeClass("sidebar-collapse").trigger("expanded.pushMenu"):$("body").addClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").hasClass("sidebar-open")?$("body").removeClass("sidebar-open").removeClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").addClass("sidebar-open").trigger("expanded.pushMenu")}),$(".content-wrapper").click(function(){$(window).width()<=b.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")}),($.AdminLTE.options.sidebarExpandOnHover||$("body").hasClass("fixed")&&$("body").hasClass("sidebar-mini"))&&this.expandOnHover()},expandOnHover:function(){var a=this,b=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>b&&a.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>b&&a.collapse()})},expand:function(){$("body").removeClass("sidebar-collapse").addClass("sidebar-expanded-on-hover")},collapse:function(){$("body").hasClass("sidebar-expanded-on-hover")&&$("body").removeClass("sidebar-expanded-on-hover").addClass("sidebar-collapse")}},$.AdminLTE.tree=function(a){var b=this,c=$.AdminLTE.options.animationSpeed;$(document).off("click",a+" li a").on("click",a+" li a",function(a){var d=$(this),e=d.next();if(e.is(".treeview-menu")&&e.is(":visible")&&!$("body").hasClass("sidebar-collapse"))e.slideUp(c,function(){e.removeClass("menu-open")}),e.parent("li").removeClass("active");else if(e.is(".treeview-menu")&&!e.is(":visible")){var f=d.parents("ul").first(),g=f.find("ul:visible").slideUp(c);g.removeClass("menu-open");var h=d.parent("li");e.slideDown(c,function(){e.addClass("menu-open"),f.find("li.active").removeClass("active"),h.addClass("active"),b.layout.fix()})}e.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var a=this,b=$.AdminLTE.options.controlSidebarOptions,c=$(b.selector),d=$(b.toggleBtnSelector);d.on("click",function(d){d.preventDefault(),c.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?a.close(c,b.slide):a.open(c,b.slide)});var e=$(".control-sidebar-bg");a._fix(e),$("body").hasClass("fixed")?a._fixForFixed(c):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");c.hasClass("collapsed-box")?(a.children(":first").removeClass(b.icons.open).addClass(b.icons.collapse),d.slideDown(b.animationSpeed,function(){c.removeClass("collapsed-box")})):(a.children(":first").removeClass(b.icons.collapse).addClass(b.icons.open),d.slideUp(b.animationSpeed,function(){c.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp(this.animationSpeed)}}}if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",animationSpeed:500,sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,sidebarExpandOnHover:!1,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!1,enableControlTreeView:!0,enableControlSidebar:!0,controlSidebarOptions:{toggleBtnSelector:"[data-action='control-sidebar']",selector:".control-sidebar",slide:!0},enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa-minus",open:"fa-plus",remove:"fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){"use strict";$("body").removeClass("hold-transition"),"undefined"!=typeof AdminLTEOptions&&$.extend(!0,$.AdminLTE.options,AdminLTEOptions);var a=$.AdminLTE.options;_init(),$.AdminLTE.layout.activate(),a.enableControlTreeView&&$.AdminLTE.tree(".sidebar"),a.enableControlSidebar&&$.AdminLTE.controlSidebar.activate(),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:a.navbarMenuHeight,alwaysVisible:!1,size:a.navbarMenuSlimscrollWidth}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu.activate(a.sidebarToggleSelector),a.enableBSToppltip&&$("body").tooltip({selector:a.BSTooltipSelector,container:"body"}),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(document).on("click",a.directChat.contactToggleSelector,function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").on("click",function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),function(a){"use strict";a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(a){return a},onLoadDone:function(a){return a}},b),f=a('
    ');return this.each(function(){if(""===e.source)return void(window.console&&window.console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.on("click",function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){"use strict";a.fn.activateBox=function(){a.AdminLTE.boxWidget.activate(this)},a.fn.toggleBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.collapse,this);a.AdminLTE.boxWidget.collapse(b)},a.fn.removeBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.remove,this);a.AdminLTE.boxWidget.remove(b)}}(jQuery),function(a){"use strict";a.fn.todolist=function(b){var c=a.extend({onCheck:function(a){return a},onUncheck:function(a){return a}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),a("input",b).is(":checked")?c.onCheck.call(b):c.onUncheck.call(b)})})}}(jQuery); \ No newline at end of file +* ================ +* Main JS application file for AdminLTE v2. This file +* should be included in all pages. It controls some layout +* options and implements exclusive AdminLTE plugins. +* +* @Author Almsaeed Studio +* @Support +* @Email +* @version 2.4.0 +* @repository git://github.com/almasaeed2010/AdminLTE.git +* @license MIT +*/ +if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");+function(a){"use strict";function b(b){return this.each(function(){var e=a(this),g=e.data(c);if(!g){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,g=new f(e,h))}if("string"==typeof g){if(void 0===g[b])throw new Error("No method named "+b);g[b]()}})}var c="lte.boxrefresh",d={source:"",params:{},trigger:".refresh-btn",content:".box-body",loadInContent:!0,responseType:"",overlayTemplate:'
    ',onLoadStart:function(){},onLoadDone:function(a){return a}},e={data:'[data-widget="box-refresh"]'},f=function(b,c){if(this.element=b,this.options=c,this.$overlay=a(c.overlay),""===c.source)throw new Error("Source url was not defined. Please specify a url in your BoxRefresh source option.");this._setUpListeners(),this.load()};f.prototype.load=function(){this._addOverlay(),this.options.onLoadStart.call(a(this)),a.get(this.options.source,this.options.params,function(b){this.options.loadInContent&&a(this.options.content).html(b),this.options.onLoadDone.call(a(this),b),this._removeOverlay()}.bind(this),""!==this.options.responseType&&this.options.responseType)},f.prototype._setUpListeners=function(){a(this.element).on("click",e.trigger,function(a){a&&a.preventDefault(),this.load()}.bind(this))},f.prototype._addOverlay=function(){a(this.element).append(this.$overlay)},f.prototype._removeOverlay=function(){a(this.element).remove(this.$overlay)};var g=a.fn.boxRefresh;a.fn.boxRefresh=b,a.fn.boxRefresh.Constructor=f,a.fn.boxRefresh.noConflict=function(){return a.fn.boxRefresh=g,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.boxwidget",d={animationSpeed:500,collapseTrigger:'[data-widget="collapse"]',removeTrigger:'[data-widget="remove"]',collapseIcon:"fa-minus",expandIcon:"fa-plus",removeIcon:"fa-times"},e={data:".box",collapsed:".collapsed-box",body:".box-body",footer:".box-footer",tools:".box-tools"},f={collapsed:"collapsed-box"},g={collapsed:"collapsed.boxwidget",expanded:"expanded.boxwidget",removed:"removed.boxwidget"},h=function(a,b){this.element=a,this.options=b,this._setUpListeners()};h.prototype.toggle=function(){a(this.element).is(e.collapsed)?this.expand():this.collapse()},h.prototype.expand=function(){var b=a.Event(g.expanded),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).removeClass(f.collapsed),a(this.element).find(e.tools).find("."+d).removeClass(d).addClass(c),a(this.element).find(e.body+", "+e.footer).slideDown(this.options.animationSpeed,function(){a(this.element).trigger(b)}.bind(this))},h.prototype.collapse=function(){var b=a.Event(g.collapsed),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).find(e.tools).find("."+c).removeClass(c).addClass(d),a(this.element).find(e.body+", "+e.footer).slideUp(this.options.animationSpeed,function(){a(this.element).addClass(f.collapsed),a(this.element).trigger(b)}.bind(this))},h.prototype.remove=function(){var b=a.Event(g.removed);a(this.element).slideUp(this.options.animationSpeed,function(){a(this.element).trigger(b),a(this.element).remove()}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.collapseTrigger,function(a){a&&a.preventDefault(),b.toggle()}),a(this.element).on("click",this.options.removeTrigger,function(a){a&&a.preventDefault(),b.remove()})};var i=a.fn.boxWidget;a.fn.boxWidget=b,a.fn.boxWidget.Constructor=h,a.fn.boxWidget.noConflict=function(){return a.fn.boxWidget=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}"string"==typeof b&&f.toggle()})}var c="lte.controlsidebar",d={slide:!0},e={sidebar:".control-sidebar",data:'[data-toggle="control-sidebar"]',open:".control-sidebar-open",bg:".control-sidebar-bg",wrapper:".wrapper",content:".content-wrapper",boxed:".layout-boxed"},f={open:"control-sidebar-open",fixed:"fixed"},g={collapsed:"collapsed.controlsidebar",expanded:"expanded.controlsidebar"},h=function(a,b){this.element=a,this.options=b,this.hasBindedResize=!1,this.init()};h.prototype.init=function(){a(this.element).is(e.data)||a(this).on("click",this.toggle),this.fix(),a(window).resize(function(){this.fix()}.bind(this))},h.prototype.toggle=function(b){b&&b.preventDefault(),this.fix(),a(e.sidebar).is(e.open)||a("body").is(e.open)?this.collapse():this.expand()},h.prototype.expand=function(){this.options.slide?a(e.sidebar).addClass(f.open):a("body").addClass(f.open),a(this.element).trigger(a.Event(g.expanded))},h.prototype.collapse=function(){a("body, "+e.sidebar).removeClass(f.open),a(this.element).trigger(a.Event(g.collapsed))},h.prototype.fix=function(){a("body").is(e.boxed)&&this._fixForBoxed(a(e.bg))},h.prototype._fixForBoxed=function(b){b.css({position:"absolute",height:a(e.wrapper).height()})};var i=a.fn.controlSidebar;a.fn.controlSidebar=b,a.fn.controlSidebar.Constructor=h,a.fn.controlSidebar.noConflict=function(){return a.fn.controlSidebar=i,this},a(document).on("click",e.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data(c);e||d.data(c,e=new f(d)),"string"==typeof b&&e.toggle(d)})}var c="lte.directchat",d={data:'[data-widget="chat-pane-toggle"]',box:".direct-chat"},e={open:"direct-chat-contacts-open"},f=function(a){this.element=a};f.prototype.toggle=function(a){a.parents(d.box).first().toggleClass(e.open)};var g=a.fn.directChat;a.fn.directChat=b,a.fn.directChat.Constructor=f,a.fn.directChat.noConflict=function(){return a.fn.directChat=g,this},a(document).on("click",d.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(h))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.layout",d={slimscroll:!0,resetHeight:!0},e={wrapper:".wrapper",contentWrapper:".content-wrapper",layoutBoxed:".layout-boxed",mainFooter:".main-footer",mainHeader:".main-header",sidebar:".sidebar",controlSidebar:".control-sidebar",fixed:".fixed",sidebarMenu:".sidebar-menu",logo:".main-header .logo"},f={fixed:"fixed",holdTransition:"hold-transition"},g=function(a){this.options=a,this.bindedResize=!1,this.activate()};g.prototype.activate=function(){this.fix(),this.fixSidebar(),a("body").removeClass(f.holdTransition),this.options.resetHeight&&a("body, html, "+e.wrapper).css({height:"auto","min-height":"100%"}),this.bindedResize||(a(window).resize(function(){this.fix(),this.fixSidebar(),a(e.logo+", "+e.sidebar).one("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend",function(){this.fix(),this.fixSidebar()}.bind(this))}.bind(this)),this.bindedResize=!0),a(e.sidebarMenu).on("expanded.tree",function(){this.fix(),this.fixSidebar()}.bind(this)),a(e.sidebarMenu).on("collapsed.tree",function(){this.fix(),this.fixSidebar()}.bind(this))},g.prototype.fix=function(){a(e.layoutBoxed+" > "+e.wrapper).css("overflow","hidden");var b=a(e.mainFooter).outerHeight()||0,c=a(e.mainHeader).outerHeight()+b,d=a(window).height(),g=a(e.sidebar).height()||0;if(a("body").hasClass(f.fixed))a(e.contentWrapper).css("min-height",d-b);else{var h;d>=g?(a(e.contentWrapper).css("min-height",d-c),h=d-c):(a(e.contentWrapper).css("min-height",g),h=g);var i=a(e.controlSidebar);void 0!==i&&i.height()>h&&a(e.contentWrapper).css("min-height",i.height())}},g.prototype.fixSidebar=function(){if(!a("body").hasClass(f.fixed))return void(void 0!==a.fn.slimScroll&&a(e.sidebar).slimScroll({destroy:!0}).height("auto"));this.options.slimscroll&&void 0!==a.fn.slimScroll&&a(e.sidebar).slimScroll({height:a(window).height()-a(e.mainHeader).height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})};var h=a.fn.layout;a.fn.layout=b,a.fn.layout.Constuctor=g,a.fn.layout.noConflict=function(){return a.fn.layout=h,this},a(window).on("load",function(){b.call(a("body"))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(g))}"toggle"===b&&f.toggle()})}var c="lte.pushmenu",d={collapseScreenSize:767,expandOnHover:!1,expandTransitionDelay:200},e={collapsed:".sidebar-collapse",open:".sidebar-open",mainSidebar:".main-sidebar",contentWrapper:".content-wrapper",searchInput:".sidebar-form .form-control",button:'[data-toggle="push-menu"]',mini:".sidebar-mini",expanded:".sidebar-expanded-on-hover",layoutFixed:".fixed"},f={collapsed:"sidebar-collapse",open:"sidebar-open",mini:"sidebar-mini",expanded:"sidebar-expanded-on-hover",expandFeature:"sidebar-mini-expand-feature",layoutFixed:"fixed"},g={expanded:"expanded.pushMenu",collapsed:"collapsed.pushMenu"},h=function(a){this.options=a,this.init()};h.prototype.init=function(){(this.options.expandOnHover||a("body").is(e.mini+e.layoutFixed))&&(this.expandOnHover(),a("body").addClass(f.expandFeature)),a(e.contentWrapper).click(function(){a(window).width()<=this.options.collapseScreenSize&&a("body").hasClass(f.open)&&this.close()}.bind(this)),a(e.searchInput).click(function(a){a.stopPropagation()})},h.prototype.toggle=function(){var b=a(window).width(),c=!a("body").hasClass(f.collapsed);b<=this.options.collapseScreenSize&&(c=a("body").hasClass(f.open)),c?this.close():this.open()},h.prototype.open=function(){a(window).width()>this.options.collapseScreenSize?a("body").removeClass(f.collapsed).trigger(a.Event(g.expanded)):a("body").addClass(f.open).trigger(a.Event(g.expanded))},h.prototype.close=function(){a(window).width()>this.options.collapseScreenSize?a("body").addClass(f.collapsed).trigger(a.Event(g.collapsed)):a("body").removeClass(f.open+" "+f.collapsed).trigger(a.Event(g.collapsed))},h.prototype.expandOnHover=function(){a(e.mainSidebar).hover(function(){a("body").is(e.mini+e.collapsed)&&a(window).width()>this.options.collapseScreenSize&&this.expand()}.bind(this),function(){a("body").is(e.expanded)&&this.collapse()}.bind(this))},h.prototype.expand=function(){setTimeout(function(){a("body").removeClass(f.collapsed).addClass(f.expanded)},this.options.expandTransitionDelay)},h.prototype.collapse=function(){setTimeout(function(){a("body").removeClass(f.expanded).addClass(f.collapsed)},this.options.expandTransitionDelay)};var i=a.fn.pushMenu;a.fn.pushMenu=b,a.fn.pushMenu.Constructor=h,a.fn.pushMenu.noConflict=function(){return a.fn.pushMenu=i,this},a(document).on("click",e.button,function(c){c.preventDefault(),b.call(a(this),"toggle")}),a(window).on("load",function(){b.call(a(e.button))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(e,h))}if("string"==typeof f){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.todolist",d={onCheck:function(a){return a},onUnCheck:function(a){return a}},e={data:'[data-widget="todo-list"]'},f={done:"done"},g=function(a,b){this.element=a,this.options=b,this._setUpListeners()};g.prototype.toggle=function(a){if(a.parents(e.li).first().toggleClass(f.done),!a.prop("checked"))return void this.unCheck(a);this.check(a)},g.prototype.check=function(a){this.options.onCheck.call(a)},g.prototype.unCheck=function(a){this.options.onUnCheck.call(a)},g.prototype._setUpListeners=function(){var b=this;a(this.element).on("change ifChanged","input:checkbox",function(){b.toggle(a(this))})};var h=a.fn.todoList;a.fn.todoList=b,a.fn.todoList.Constructor=g,a.fn.todoList.noConflict=function(){return a.fn.todoList=h,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this);if(!e.data(c)){var f=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,new h(e,f))}})}var c="lte.tree",d={animationSpeed:500,accordion:!0,followLink:!1,trigger:".treeview a"},e={tree:".tree",treeview:".treeview",treeviewMenu:".treeview-menu",open:".menu-open, .active",li:"li",data:'[data-widget="tree"]',active:".active"},f={open:"menu-open",tree:"tree"},g={collapsed:"collapsed.tree",expanded:"expanded.tree"},h=function(b,c){this.element=b,this.options=c,a(this.element).addClass(f.tree),a(e.treeview+e.active,this.element).addClass(f.open),this._setUpListeners()};h.prototype.toggle=function(a,b){var c=a.next(e.treeviewMenu),d=a.parent(),g=d.hasClass(f.open);d.is(e.treeview)&&(this.options.followLink&&"#"!==a.attr("href")||b.preventDefault(),g?this.collapse(c,d):this.expand(c,d))},h.prototype.expand=function(b,c){var d=a.Event(g.expanded);if(this.options.accordion){var h=c.siblings(e.open),i=h.children(e.treeviewMenu);this.collapse(i,h)}c.addClass(f.open),b.slideDown(this.options.animationSpeed,function(){a(this.element).trigger(d)}.bind(this))},h.prototype.collapse=function(b,c){var d=a.Event(g.collapsed);b.find(e.open).removeClass(f.open),c.removeClass(f.open),b.slideUp(this.options.animationSpeed,function(){b.find(e.open+" > "+e.treeview).slideUp(),a(this.element).trigger(d)}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.trigger,function(c){b.toggle(a(this),c)})};var i=a.fn.tree;a.fn.tree=b,a.fn.tree.Constructor=h,a.fn.tree.noConflict=function(){return a.fn.tree=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery); \ No newline at end of file diff --git a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue-light.min.css b/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue-light.min.css deleted file mode 100755 index c41ca33fa..000000000 --- a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue-light.min.css +++ /dev/null @@ -1 +0,0 @@ -.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file diff --git a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css b/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css index 44524fe38..cdbef24bf 100755 --- a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css +++ b/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css @@ -1 +1 @@ -.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file +.skin-blue .main-header .navbar{background-color:#10529f}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#0e4688}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#0e4688}}.skin-blue .main-header .logo{background-color:#0e4688;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#0d4483}.skin-blue .main-header li.user-header{background-color:#10529f}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#191b22}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#444a5d;background:#101216}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a,.skin-blue .sidebar-menu>li.menu-open>a{color:#fff;background:#15161c}.skin-blue .sidebar-menu>li.active>a{border-left-color:#10529f}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#242731}.skin-blue .sidebar a{color:#abb0c2}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .sidebar-menu .treeview-menu>li>a{color:#7f87a1}.skin-blue .sidebar-menu .treeview-menu>li.active>a,.skin-blue .sidebar-menu .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #2f323f;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#2f323f;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#10529f;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#10509a} diff --git a/public/themes/pterodactyl/vendor/particlesjs/particles.json b/public/themes/pterodactyl/vendor/particlesjs/particles.json new file mode 100644 index 000000000..abc60c899 --- /dev/null +++ b/public/themes/pterodactyl/vendor/particlesjs/particles.json @@ -0,0 +1,110 @@ +{ + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.40246529723245905, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 1, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 5, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "bounce": false, + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "window", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "repulse" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200, + "duration": 0.4 + }, + "push": { + "particles_nb": 4 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true +} diff --git a/public/themes/pterodactyl/vendor/particlesjs/particles.min.js b/public/themes/pterodactyl/vendor/particlesjs/particles.min.js new file mode 100644 index 000000000..1b204b7bd --- /dev/null +++ b/public/themes/pterodactyl/vendor/particlesjs/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; diff --git a/resources/lang/de/admin/nests.php b/resources/lang/de/admin/nests.php new file mode 100644 index 000000000..7fb11866b --- /dev/null +++ b/resources/lang/de/admin/nests.php @@ -0,0 +1,26 @@ + [ + 'created' => 'Das neue Nest :name wurde erstellt', + 'deleted' => 'Nest erfolgreich gelöscht.', + 'updated' => 'Nest erfolgreich bearbeitet.', + ], + 'eggs' => [ + 'notices' => [ + 'imported' => 'Eg erfolgreich importiert.', + 'updated_via_import' => 'Dieses Egg wurde aktualisiert', + 'deleted' => 'Dieses Egg wurde gelöscht', + 'updated' => 'Egg wurde erfolgreich bearbeitet.', + 'script_updated' => 'Egg install script wurde aktualisiert.', + 'egg_created' => 'A new egg was laid successfully. You will need to restart any running daemons to apply this new egg.', + ], + ], + 'variables' => [ + 'notices' => [ + 'variable_deleted' => 'Die Variable ":variable" wurde gelöscht', + 'variable_updated' => 'Die Variable ":variable" wurde aktualisiert du musst die Rebuild Funktion jedes Servers benutzen.', + 'variable_created' => 'Eine neue Variable wurde erstellt und dem Egg zugewiesen.', + ], + ], +]; diff --git a/resources/lang/de/admin/node.php b/resources/lang/de/admin/node.php new file mode 100644 index 000000000..218e5901e --- /dev/null +++ b/resources/lang/de/admin/node.php @@ -0,0 +1,16 @@ + [ + 'fqdn_not_resolvable' => 'Diese FQDN scheint nicht auf eine IP weiterzuleiten.', + 'fqdn_required_for_ssl' => 'Eine Domain wird für die SSL Funktion benötigt.', + ], + 'notices' => [ + 'allocations_added' => 'Allocations wurden zu diesem Node erfolgreich hinzugefügt.', + 'node_deleted' => 'Node wurde erfolgreich gelöscht.', + 'location_required' => 'Du brauchst mindestens eine Location um einen Node zu konfigurieren.', + 'node_created' => 'Node erfolgreich erstellt bitte paste die Config aus dem Configuration tab in die Datei /srv/daemon/config/core.json', + 'node_updated' => 'Node erfolgreich bearbeitet', + 'unallocated_deleted' => 'Alle unbenutzen Ports für:ip gelöscht.', + ], +]; diff --git a/resources/lang/de/admin/pack.php b/resources/lang/de/admin/pack.php new file mode 100644 index 000000000..c2cd11eb0 --- /dev/null +++ b/resources/lang/de/admin/pack.php @@ -0,0 +1,9 @@ + [ + 'pack_updated' => 'Pack erfolgreich aktualisiert', + 'pack_deleted' => 'Pack ":name" erfolgreich gelöscht.', + 'pack_created' => 'Ein neues Pack wurde erfolgreich erstellt.', + ], +]; diff --git a/resources/lang/de/admin/user.php b/resources/lang/de/admin/user.php new file mode 100644 index 000000000..6d6a263f6 --- /dev/null +++ b/resources/lang/de/admin/user.php @@ -0,0 +1,11 @@ + [ + 'user_has_servers' => 'Es kann kein Benutzer mit einem aktiven Server gelöscht werden.', + ], + 'notices' => [ + 'account_created' => 'Der Account wurde erfolgreich erstellt.', + 'account_updated' => 'Der Account wurde erfolgreich bearbeitet.', + ], +]; diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php new file mode 100644 index 000000000..14d9d210c --- /dev/null +++ b/resources/lang/de/auth.php @@ -0,0 +1,22 @@ + 'Du bist nicht autorisiert diese Aktion auszuführen0.', + 'auth_error' => 'Es gab ein Problem während du dich anmelden wolltest.', + 'authentication_required' => 'Du musst angemeldet sein um diese Aktion auszuführen.', + 'remember_me' => 'Login merken', + 'sign_in' => 'Anmelden', + 'forgot_password' => 'Ich habe mein Passwort vergessen!', + 'request_reset_text' => 'Du hast dein Passwort vergessen? Das ist keinWeltuntergang! Gib einfach deine Email hier an.', + 'reset_password_text' => 'Passwort zurücksetzen.', + 'reset_password' => 'Passwort zurücksetzen', + 'email_sent' => 'Dir wurde eine Email mit weiteren Informationen geschickt', + 'failed' => 'Die eingegebenen Informationen waren falsch.', + 'throttle' => 'Du hast zu oft versucht dich anzumalen bitte warte noch :seconds Sekunden.', + 'password_requirements' => 'Passwörter müssen Zahlen, Klein-, Großbuchstaben enthalten und mindestens 8 Zeichen lang sein.', + 'request_reset' => 'Account finden', + '2fa_required' => '2-Factor Authentifizierung', + '2fa_failed' => 'Der 2FA Code war ungültig.', + 'totp_failed' => 'Der TOTP Code war ungültig.', + '2fa_must_be_enabled' => 'Der Administrator hat festgelegt dass jeder Account 2FA benutzen muss.', +]; diff --git a/resources/lang/de/base.php b/resources/lang/de/base.php new file mode 100644 index 000000000..b482e9af5 --- /dev/null +++ b/resources/lang/de/base.php @@ -0,0 +1,242 @@ + 'Es gab ein Problem mit einer oder mehreren deriner Eingaben.', + 'errors' => [ + 'return' => 'Gehe zurück zu deiner voherigen Seite', + 'home' => 'Gehe zur Startseite', + '403' => [ + 'header' => 'Forbidden', + 'desc' => 'Du darfst diese Seite nicht öffnen.', + ], + '404' => [ + 'header' => 'File Not Found', + 'desc' => 'Es scheint als würde diese Seite nicht exsistieren.', + ], + 'installing' => [ + 'header' => 'Server Installing', + 'desc' => 'Der Server wird derzeit noch installiert bitte versuche es später erneut.', + ], + 'suspended' => [ + 'header' => 'Server Suspended', + 'desc' => 'Dieser Server wurde von einem Administrator gesperrt.', + ], + ], + 'index' => [ + 'header' => 'Deine Server', + 'header_sub' => 'Server auf die du Zugriff hast.', + 'list' => 'Server List', + ], + 'api' => [ + 'index' => [ + 'header' => 'API Access (This Site is not translated because we think that the english of developers is good enough)', + 'header_sub' => 'Manage your API access keys.', + 'list' => 'API Keys', + 'create_new' => 'Create New API key', + 'keypair_created' => 'An API Key-Pair has been generated. Your API secret token is :token. Please take note of this key as it will not be displayed again.', + ], + 'new' => [ + 'header' => 'New API Key', + 'header_sub' => 'Create a new API access key', + 'form_title' => 'Details', + 'descriptive_memo' => [ + 'title' => 'Descriptive Memo', + 'description' => 'Enter a brief description of what this API key will be used for.', + ], + 'allowed_ips' => [ + 'title' => 'Allowed IPs', + 'description' => 'Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.', + ], + ], + 'permissions' => [ + 'user' => [ + 'server_header' => 'User Rechte', + 'server' => [ + 'list' => [ + 'title' => 'List Servers', + 'desc' => 'Der user darf seine Serverliste ansehen.', + ], + 'view' => [ + 'title' => 'View Server', + 'desc' => 'Der User darf detaillierte Informationen über seine Server sehen.', + ], + 'power' => [ + 'title' => 'Toggle Power', + 'desc' => 'Der User darf den Server starten/stoppen/restartet.', + ], + 'command' => [ + 'title' => 'Send Command', + 'desc' => 'Der User hat Zugriff auf die Server Console.', + ], + ], + ], + 'admin' => [ + 'server_header' => 'Server Control', + 'server' => [ + 'list' => [ + 'title' => 'List Servers', + 'desc' => 'Der User darf alle Server dieser Instanz sehen.', + ], + 'view' => [ + 'title' => 'View Server', + 'desc' => 'Der user darf detaillierte Informationen zu allen Servern dieser Instanz sehen.', + ], + 'delete' => [ + 'title' => 'Delete Server', + 'desc' => 'Der User darf Server löschen.', + ], + 'create' => [ + 'title' => 'Create Server', + 'desc' => 'Der User darf Server erstellen.', + ], + 'edit-details' => [ + 'title' => 'Edit Server Details', + 'desc' => 'Der User darf die Server EInstellungen bearbeiten.', + ], + 'edit-container' => [ + 'title' => 'Edit Server Container', + 'desc' => 'Der User darf die Container Einstellungen des Servers verändern.', + ], + 'suspend' => [ + 'title' => 'Suspend Server', + 'desc' => 'Der User darf Server sperren.', + ], + 'install' => [ + 'title' => 'Toggle Install Status', + 'desc' => 'Der User darf den Installationstatus bearbeiten', + ], + 'rebuild' => [ + 'title' => 'Rebuild Server', + 'desc' => 'Der User darf den Server ner erstellen', + ], + 'edit-build' => [ + 'title' => 'Edit Server Build', + 'desc' => 'Der User darf Server einstellungen bearbeiten.', + ], + 'edit-startup' => [ + 'title' => 'Edit Server Startup', + 'desc' => 'Der User darf die Startparameter ändern.', + ], + ], + 'location_header' => 'Location Control', + 'location' => [ + 'list' => [ + 'title' => 'List Locations', + 'desc' => 'Der User darf alle Locations sehen.', + ], + ], + 'node_header' => 'Node Control', + 'node' => [ + 'list' => [ + 'title' => 'List Nodes', + 'desc' => 'Der User darf alle nodes sehen', + ], + 'view' => [ + 'title' => 'View Node', + 'desc' => 'Der User darf detaillierte Details eines Nodes sehen', + ], + 'view-config' => [ + 'title' => 'View Node Configuration', + 'desc' => 'Danger. Der User kann die Konfiguration eines Node sehen.', + ], + 'create' => [ + 'title' => 'Create Node', + 'desc' => 'Der User aknn ein Node erstellen.', + ], + 'delete' => [ + 'title' => 'Delete Node', + 'desc' => 'Allows User kann ein Node löschen.', + ], + ], + 'user_header' => 'User Control', + 'user' => [ + 'list' => [ + 'title' => 'List Users', + 'desc' => 'Der User kann alle User sehen.', + ], + 'view' => [ + 'title' => 'View User', + 'desc' => 'Der User kann detaillierte Informationen der User sehen.', + ], + 'create' => [ + 'title' => 'Create User', + 'desc' => 'Der User kann einen User erstellen.', + ], + 'edit' => [ + 'title' => 'Update User', + 'desc' => 'Der User kann einen User bearbeiten.', + ], + 'delete' => [ + 'title' => 'Delete User', + 'desc' => 'Der User kann einen Server löschen.', + ], + ], + 'service_header' => 'Service Control', + 'service' => [ + 'list' => [ + 'title' => 'List Service', + 'desc' => 'Der User kann alle Services sehen.', + ], + 'view' => [ + 'title' => 'View Service', + 'desc' => 'Der user kann detaillierte Informationen über einen Service sehen.', + ], + ], + 'option_header' => 'Option Control', + 'option' => [ + 'list' => [ + 'title' => 'List Options', + 'desc' => '', + ], + 'view' => [ + 'title' => 'View Option', + 'desc' => '', + ], + ], + 'pack_header' => 'Pack Control', + 'pack' => [ + 'list' => [ + 'title' => 'List Packs', + 'desc' => '', + ], + 'view' => [ + 'title' => 'View Pack', + 'desc' => '', + ], + ], + ], + ], + ], + 'account' => [ + 'details_updated' => 'Dein Account wurde erfolgreich bearbeitet.', + 'invalid_password' => 'Das Passwort war leider ungültig.', + 'header' => 'Dein Account', + 'header_sub' => 'Account Details verwalten.', + 'update_pass' => 'Passwort ändern', + 'update_email' => 'Email ändern', + 'current_password' => 'Aktuelles Passwort', + 'new_password' => 'Neues Passwort', + 'new_password_again' => 'Neues Passwort wiederholen', + 'new_email' => 'Neue Email Adresse', + 'first_name' => 'Vornahme', + 'last_name' => 'Nachname', + 'update_identitity' => 'Account bearbeiten', + 'username_help' => 'Dein Username darf nicht bereits vergeben sein oder folgende Zeichen enthakten: :requirements.', + ], + 'security' => [ + 'session_mgmt_disabled' => 'Der Administrator hat diese Funktion deaktiviert.', + 'header' => 'Account Sicherheit', + 'header_sub' => '2-Factor-Authentification aktivieren.', + 'sessions' => 'Aktieve Sessions', + '2fa_header' => '2-Factor Authentication', + '2fa_token_help' => 'Bitte gebe den 2FA Code von deiner 2FA APP ein (Google Authenticatior, Authy, etc.).', + 'disable_2fa' => '2-Factor-Authentification deaktivieren', + '2fa_enabled' => 'Die 2-Factor-Authentification ist aktiviert und du wirst nach einem Sicherheits code beim anmelden gefragt + ', + '2fa_disabled' => 'Die 2-Factor Authentication wurde deaktiviert', + 'enable_2fa' => '2-Factor-Authentification aktivieren.', + '2fa_qr' => '2FA konfigurieren', + '2fa_checkpoint_help' => 'Öffne deine 2FA APP und scanne diesen QR Code.', + '2fa_disable_error' => 'Die 2-Factor-Authentification wurde nicht aktiviert da dein Code ungültig war.', + ], +]; diff --git a/resources/lang/de/command/messages.php b/resources/lang/de/command/messages.php new file mode 100644 index 000000000..ec87e41b1 --- /dev/null +++ b/resources/lang/de/command/messages.php @@ -0,0 +1,84 @@ + [ + 'no_location_found' => 'Shortcode wurde nicht gefunden.', + 'ask_short' => 'Location Short Code', + 'ask_long' => 'Location Beschreibung', + 'created' => 'Neue location (:name) mit der id :id erstellt.', + 'deleted' => 'Location gelöscht.', + ], + 'user' => [ + 'search_users' => 'Gebe einen Nutzernamen, eine UUID oder eine Email an', + 'select_search_user' => 'ID des Users (Enter \'0\' to re-search)', + 'deleted' => 'Benutzer erfolgreich gelöscht.', + 'confirm_delete' => 'Bist du dir wirklich sicher?', + 'no_users_found' => 'Es wurden keine User gefunden.', + 'multiple_found' => 'Es wurden mehrere Accounts gefunden.', + 'ask_admin' => 'Is this user an administrator?', + 'ask_email' => 'Email Adresse', + 'ask_username' => 'Username', + 'ask_name_first' => 'Vornamee', + 'ask_name_last' => 'Nachname', + 'ask_password' => 'Password', + 'ask_password_tip' => 'Wenn du dass wirklich tun willst drücke Strg+c und benutze das `--no-password` flag.', + 'ask_password_help' => 'Das Passwort muss Zahlen, Groß- und Kleinbuchstaben enthalten und mindestens 8 Zeichen lang sein.', + '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 wurde für :email deaktivier.', + ], + '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', + ], + 'environment' => [ + 'mail' => [ + 'ask_smtp_host' => 'SMTP Host (e.g. smtp.google.com)', + 'ask_smtp_port' => 'SMTP Port', + 'ask_smtp_username' => 'SMTP Username', + 'ask_smtp_password' => 'SMTP Password', + 'ask_mailgun_domain' => 'Mailgun Domain', + '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' => [ + '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', + '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 inaccessable 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?', + ], + ], +]; diff --git a/resources/lang/de/exceptions.php b/resources/lang/de/exceptions.php new file mode 100644 index 000000000..19ae86554 --- /dev/null +++ b/resources/lang/de/exceptions.php @@ -0,0 +1,55 @@ + 'Es gab einen Fehler bei der Verbindung mit dem Daemon. Ausgabe: HTTP/:code response code. Dieser Fehler wurde geloggt.', + 'node' => [ + 'servers_attached' => 'Ein node musst Server konfiguriert haben um gelöscht zu werden.', + 'daemon_off_config_updated' => 'Die COnfiguration wurde aktualisiert! Du musst allerdings die config neu auf dem Server bearbeiten.', + ], + 'allocations' => [ + 'too_many_ports' => 'Du kannst leider nicht mehr als 1000 Ports gleichzeitig hinzufügen', + 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', + 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', + ], + 'nest' => [ + 'delete_has_servers' => 'A Nest with active servers attached to it cannot be deleted from the Panel.', + 'egg' => [ + 'delete_has_servers' => 'An Egg with active servers attached to it cannot be deleted from the Panel.', + 'invalid_copy_id' => 'The Egg selected for copying a script from either does not exist, or is copying a script itself.', + 'must_be_child' => 'The "Copy Settings From" directive for this Egg must be a child option for the selected Nest.', + 'has_children' => 'This Egg is a parent to one or more other Eggs. Please delete those Eggs before deleting this Egg.', + ], + 'variables' => [ + 'env_not_unique' => 'The environment variable :name must be unique to this Egg.', + 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', + ], + 'importer' => [ + 'json_error' => 'There was an error while attempting to parse the JSON file: :error.', + 'file_error' => 'The JSON file provided was not valid.', + 'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.', + ], + ], + 'packs' => [ + 'delete_has_servers' => 'Ein Pack kann nicht gelöscht werden wenn es von einem aktieven Server benutzt wird.', + 'update_has_servers' => 'Ein Pack kann nicht bearbeitet werden wenn es von einem aktieven Server benutzt wird..', + 'invalid_upload' => 'Die Datei scheint ungültig zu sein.', + 'invalid_mime' => 'Die Datei hat nicht den angeforderten Typ: :type', + 'unreadable' => 'Das Archiv konnte nicht geöffnet werden.', + 'zip_extraction' => 'Es gab ein Problem beim Entpacken des Archivs.', + 'invalid_archive_exception' => 'Die Pack Datei scheint keine import.json zu enthalten.', + ], + 'subusers' => [ + 'editing_self' => 'Du darfst deinen eigenen SUbuser nicht bearbeiten.', + 'user_is_owner' => 'Du kannst den Owner nicht als Subuser hinzufügen.', + 'subuser_exists' => 'Diese Email ist bereits registriert.', + ], + 'databases' => [ + 'delete_has_databases' => 'Es kann keine Datenbank gelöscht werden die von einem aktivien Server gelöscht wird.', + ], + 'tasks' => [ + 'chain_interval_too_long' => 'The maximum interval time for a chained task is 15 minutes.', + ], + 'locations' => [ + 'has_nodes' => 'Es kann keine Location gelöscht werden die von einem Node benutzt wird', + ], +]; diff --git a/resources/lang/de/navigation.php b/resources/lang/de/navigation.php new file mode 100644 index 000000000..104b968a8 --- /dev/null +++ b/resources/lang/de/navigation.php @@ -0,0 +1,31 @@ + 'Startseite', + 'account' => [ + 'header' => 'ACCOUNT VERWALTUNG', + 'my_account' => 'Mein Account', + 'security_controls' => 'Sicherheit', + 'api_access' => 'API Access', + 'my_servers' => 'Meine Server', + ], + 'server' => [ + 'header' => 'SERVER VERWALTUNG', + 'console' => 'Console', + 'console-pop' => 'Fullscreen Console', + 'file_management' => 'File Management', + 'file_browser' => 'File Browser', + 'create_file' => 'Datei erstellen', + 'upload_files' => 'Datei hochladen', + 'subusers' => 'Subusers', + 'schedules' => 'Geplante Tasks', + 'configuration' => 'Konfiguration', + 'port_allocations' => 'Allocation Settings', + 'sftp_settings' => 'SFTP Einstellungen', + 'startup_parameters' => 'Startup Parameter', + 'databases' => 'Datenbaken', + 'edit_file' => 'Datei bearbeiten', + 'admin_header' => 'ADMINISTRATIVE', + 'admin' => 'Server Konfiguration', + ], +]; diff --git a/resources/lang/de/pagination.php b/resources/lang/de/pagination.php new file mode 100644 index 000000000..6ae8f4a34 --- /dev/null +++ b/resources/lang/de/pagination.php @@ -0,0 +1,16 @@ + '« Vorherige', + 'next' => 'Nächste »', +]; diff --git a/resources/lang/de/passwords.php b/resources/lang/de/passwords.php new file mode 100644 index 000000000..64c26b867 --- /dev/null +++ b/resources/lang/de/passwords.php @@ -0,0 +1,19 @@ + 'Dein Passwort muss 6 Zeichen lang sein und der Wiederholung entsprechen.', + 'reset' => 'Dein Passwort wurde zurückgesetzt!', + 'sent' => 'Dir wurde eine Email mit weiteren Informationen geschickt!', + 'token' => 'Der Token war ungültig', + 'user' => 'Es gibt keinen User mit dieser Email.', +]; diff --git a/resources/lang/de/server.php b/resources/lang/de/server.php new file mode 100644 index 000000000..d207dc4ad --- /dev/null +++ b/resources/lang/de/server.php @@ -0,0 +1,313 @@ + [ + 'title' => 'Server :name', + 'header' => 'Server Konsole', + 'header_sub' => 'Verwalte deinen Server in Echtzeit.', + ], + 'schedule' => [ + 'header' => 'Schedule Manager', + 'header_sub' => 'Erstelle geplante Aktionen.', + 'current' => 'Derzeitige Aktionen', + 'new' => [ + 'header' => 'Neue Aktion erstellen', + 'header_sub' => 'Erstelle eine neue Gruppe an Aktionen.', + 'submit' => 'Aktion erstellen', + ], + 'manage' => [ + 'header' => 'Aktion verwalten', + 'submit' => 'Aktion bearbeiten', + 'delete' => 'Aktion löschen', + ], + 'task' => [ + 'time' => 'Nach', + 'action' => 'Aktion ausführen', + 'payload' => 'With Payload', + 'add_more' => 'Weitere Aktion', + ], + 'actions' => [ + 'command' => 'Command ausführen', + 'power' => 'Power Aktion', + ], + 'unnamed' => 'Unnamed Schedule', + 'setup' => 'Schedule Setup', + 'day_of_week' => 'Day of Week', + 'day_of_month' => 'Day of Month', + 'hour' => 'Hour of Day', + 'minute' => 'Minute of Hour', + 'time_help' => 'Dieses System unterstützt dern Cronjob Syntax.', + 'task_help' => 'Times for tasks are relative to the previously defined task. Each schedule may have no more than 5 tasks assigned to it and tasks may not be scheduled more than 15 minutes apart.', + ], + 'tasks' => [ + 'task_created' => 'Aktion erfolgreich erstellt.', + 'task_updated' => 'Aktion bearbeitet.', + 'header' => 'Scheduled Tasks', + 'header_sub' => 'Automatisiere deinen Server.', + 'current' => 'Aktuelle Aktionen', + 'actions' => [ + 'command' => 'Command ausführen', + 'power' => 'Power Aktion senden', + ], + 'new_task' => 'Neue Aktion erstellen', + 'toggle' => 'Status ändern', + 'new' => [ + 'header' => 'Neue Aktion', + 'header_sub' => 'Neuen Aktion erstellen.', + 'task_name' => 'Name', + 'day_of_week' => 'Tag einer Woche', + 'custom' => 'Custom Value', + 'day_of_month' => 'Tag eines Monats', + 'hour' => 'Stunde', + 'minute' => 'Minute', + 'sun' => 'Sontag', + 'mon' => 'Montad', + 'tues' => 'Dienstag', + 'wed' => 'Mittwoch', + 'thurs' => 'Donnerstag', + 'fri' => 'Freitag', + 'sat' => 'Samstag', + 'submit' => 'Absenden', + 'type' => 'TTyp', + 'chain_then' => 'Then, After', + 'chain_do' => 'Do', + 'chain_arguments' => 'With Arguments', + 'payload' => 'Task Payload', + 'payload_help' => 'Wenn du die send command Methode ausgewählt hast wird ein Command zur angegebenen Zeit ausgeführt.', + ], + 'edit' => [ + 'header' => 'Aktion beareiten', + 'submit' => 'Abschicken', + ], + ], + 'users' => [ + 'header' => 'User verwalten', + 'header_sub' => 'Bestimme wer den Server verwalten kann.', + 'configure' => 'Rechte einstellen', + 'list' => 'Account Liste', + 'add' => 'Neuen User erstellen', + 'update' => 'User bearbeiten', + 'user_assigned' => 'User an einen Server gebunden.', + 'user_updated' => 'User Rechte erfolgreich aktualisiert.', + 'edit' => [ + 'header' => 'User bearbeiten', + 'header_sub' => 'Bearbeite den Zugriff eines Users auf deine Server.', + ], + 'new' => [ + 'header' => 'Neuen User erstellen', + 'header_sub' => 'Erstelle einen neuen User un gebe ihm Zugirff auf einen Server.', + 'email' => 'Email Address', + 'email_help' => 'Email Adresse für Einladungs mail.', + 'power_header' => 'Power Verwaltung', + 'file_header' => 'Datein Verwaltung', + 'subuser_header' => 'Subuser Verwaltung', + 'server_header' => 'Server Verwaltung', + 'task_header' => 'Schedule Verwaltung', + 'database_header' => 'Database Verwaltung', + 'power_start' => [ + 'title' => 'Start Server', + 'description' => 'Der User darf den Server starten.', + ], + 'power_stop' => [ + 'title' => 'Stop Server', + 'description' => 'Der User darf den Server stoppen.', + ], + 'power_restart' => [ + 'title' => 'Restart Server', + 'description' => 'Der User darf den Server restarten.', + ], + 'power_kill' => [ + 'title' => 'Kill Server', + 'description' => 'Der User darf den Prozess des Servers töten.', + ], + 'send_command' => [ + 'title' => 'Send Console Command', + 'description' => 'Der User darf die Konsole benutzen.', + ], + 'view_sftp' => [ + 'title' => 'SFTP erlaubt', + 'description' => 'Ermöglicht dem Benutzer, eine Verbindung mit dem vom Daemon bereitgestellten SFTP-Server herzustellen.', + ], + 'list_files' => [ + 'title' => 'List Files', + 'description' => 'Der User darf die Server-Dateien sehen.', + ], + 'edit_files' => [ + 'title' => 'Edit Files', + 'description' => 'Der User darf die Server-Dateien sehen. SFTP ist von dieser Erlaubnis nicht betroffen.', + ], + 'save_files' => [ + 'title' => 'Save Files', + 'description' => 'Der User darf die Server-Dateien bearbeiten. SFTP ist von dieser Erlaubnis nicht betroffen.', + ], + 'move_files' => [ + 'title' => 'Rename & Move Files', + 'description' => 'Der User darf die Server-Dateien ubenennen und verschieben.', + ], + 'copy_files' => [ + 'title' => 'Copy Files', + 'description' => 'Der User darf die Server-Dateien kopieren.', + ], + 'compress_files' => [ + 'title' => 'Compress Files', + 'description' => 'Der User darf die Server-Dateien komprimieren(zip).', + ], + 'decompress_files' => [ + 'title' => 'Decompress Files', + 'description' => 'Der User darf zip Archive entpacken.', + ], + 'create_files' => [ + 'title' => 'Create Files', + 'description' => 'Der User darf Server-Dateien erstellen.', + ], + 'upload_files' => [ + 'title' => 'Upload Files', + 'description' => 'Der User darf Server-Dateien hochladen.', + ], + 'delete_files' => [ + 'title' => 'Delete Files', + 'description' => 'Der User darf Server-Dateien löschen.', + ], + 'download_files' => [ + 'title' => 'Download Files', + 'description' => 'Der User darf Server-Dateien herunterladen.', + ], + 'list_subusers' => [ + 'title' => 'List Subusers', + 'description' => 'Der User darf Subuser sehen.', + ], + 'view_subuser' => [ + 'title' => 'View Subuser', + 'description' => 'Der User darf Subuser genauer sehen.', + ], + 'edit_subuser' => [ + 'title' => 'Edit Subuser', + 'description' => 'Der User darf Subuser bearbeiten.', + ], + 'create_subuser' => [ + 'title' => 'Create Subuser', + 'description' => 'Der User darf Subuser erstellen.', + ], + 'delete_subuser' => [ + 'title' => 'Delete Subuser', + 'description' => 'Der User darf Subuser löschen.', + ], + 'view_allocations' => [ + 'title' => 'View Allocations', + 'description' => 'Allows user to view all of the IPs and ports assigned to a server.', + ], + 'edit_allocation' => [ + 'title' => 'Edit Default Connection', + 'description' => 'Allows user to change the default connection allocation to use for a server.', + ], + 'view_startup' => [ + 'title' => 'View Startup Command', + 'description' => 'Allows user to view the startup command and associated variables for a server.', + ], + 'edit_startup' => [ + 'title' => 'Edit Startup Command', + 'description' => 'Allows a user to modify startup variables for a server.', + ], + 'list_schedules' => [ + 'title' => 'List Schedules', + 'description' => 'Der User darf geplante Aktionen für den Server sehen.', + ], + 'view_schedule' => [ + 'title' => 'View Schedule', + 'description' => 'Der User darf eine Aktion ansehen.', + ], + 'toggle_schedule' => [ + 'title' => 'Toggle Schedule', + 'description' => 'Der User darf geplante Aktionen für den Server de-/aktivieren.', + ], + 'queue_schedule' => [ + 'title' => 'Queue Schedule', + 'description' => 'Allows a user to queue a schedule to run it\'s tasks on the next process cycle.', + ], + 'edit_schedule' => [ + 'title' => 'Edit Schedule', + 'description' => 'Der User darf geplante Aktionen für den Server bearbeiten.', + ], + 'create_schedule' => [ + 'title' => 'Create Schedule', + 'description' => 'Der User darf geplante Aktionen für den Server erstellen.', + ], + 'delete_schedule' => [ + 'title' => 'Delete Schedule', + 'description' => 'Der User darf geplante Aktionen für den Server löschen.', + ], + 'view_databases' => [ + 'title' => 'View Database Details', + 'description' => 'Der User darf die Datenbankinformationen sehen.', + ], + 'reset_db_password' => [ + 'title' => 'Reset Database Password', + 'description' => 'Der User darf das Datenbankpasswort zurücksetzen.', + ], + ], + ], + 'files' => [ + 'exceptions' => [ + 'invalid_mime' => 'Diese Datei kann leider nicht bearbeitet werden', + 'max_size' => 'Diese Datei ist zu groß um bearbeitet zu werden.', + ], + 'header' => 'Datei Manager', + 'header_sub' => 'Verwalte deine Dateien.', + 'loading' => 'Datein werden geladen. Bitte warten...', + 'path' => 'Wenn du Ordner erstellst solltest du :path als Basis Ordner verwenden! Der maximale Upload beträgt: :size.', + 'seconds_ago' => 'Sekunden her', + 'file_name' => 'Dateiname', + 'size' => 'Größe', + 'last_modified' => 'Zuletzt bearbeitet', + 'add_new' => 'Neue Datei erstellen', + 'add_folder' => 'Neuen Ordner ertsllen', + 'mass_actions' => 'Massenaktionen', + 'delete' => 'löschen', + 'edit' => [ + 'header' => 'Datei bearbeiten', + 'header_sub' => 'Bearbeite Dateien direkt vom Browser aus.', + 'save' => 'Datei speichern', + 'return' => 'Zurück zum Datei Manager', + ], + 'add' => [ + 'header' => 'Neue Datei', + 'header_sub' => 'Erstelle eine neue Datei.', + 'name' => 'Dateiname', + 'create' => 'Datei erstellen', + ], + ], + 'config' => [ + 'startup' => [ + 'header' => 'Start Konfiguration', + 'header_sub' => 'Bearbeite die Startparameter des Serves.', + 'command' => 'Startup Command', + 'edit_params' => 'Parameter bearbeiten', + 'update' => 'Absenden', + 'startup_regex' => 'Input Rules', + 'edited' => 'Die Einstellungen wurden gespeichert und werden beim nächsten Serverstart verwendet.', + ], + 'sftp' => [ + 'header' => 'SFTP Information', + 'header_sub' => 'Details für eine SFTP verbindung.', + 'details' => 'SFTP Details', + 'conn_addr' => 'Adresse', + 'warning' => 'Bitte benutze SFTP und nicht FTP!.', + ], + 'database' => [ + 'header' => 'Datenbanken', + 'header_sub' => 'Alle für diesen Server verfügbaren Datenbanken.', + 'your_dbs' => 'Deine Datenbanken', + 'host' => 'MySQL Host', + 'reset_password' => 'Passwort zurücksetzen', + 'no_dbs' => 'Du hast leider keine Datenbanken.', + 'add_db' => 'Datenbank hinzufügen.', + ], + 'allocation' => [ + 'header' => 'Server Allocations', + 'header_sub' => 'Control the IPs and ports available on this server.', + 'available' => 'Available Allocations', + 'help' => 'Allocation Help', + 'help_text' => 'The list to the left includes all available IPs and ports that are open for your server to use for incoming connections.', + ], + ], +]; diff --git a/resources/lang/de/strings.php b/resources/lang/de/strings.php new file mode 100644 index 000000000..7ea006169 --- /dev/null +++ b/resources/lang/de/strings.php @@ -0,0 +1,86 @@ + 'Email', + 'user_identifier' => 'Username oder Email', + 'password' => 'Passwort', + 'confirm_password' => 'Passwort bestätigen', + 'login' => 'Login', + 'home' => 'Startseite', + 'servers' => 'Server', + 'id' => 'ID', + 'name' => 'Name', + 'node' => 'Node', + 'connection' => 'Verbindung', + 'memory' => 'Memory', + 'cpu' => 'CPU', + 'status' => 'Status', + 'search' => 'Suche', + 'suspended' => 'Gespert', + 'account' => 'Account', + 'security' => 'Sicherheit', + 'ip' => 'IP Adresse', + 'last_activity' => 'Letzte Aktivität', + 'revoke' => 'Zurückziehen', + '2fa_token' => 'Authentifizierungs Code', + 'submit' => 'Absenden', + 'close' => 'Schließen', + 'settings' => 'Einstellungen', + 'configuration' => 'Konfiguration', + 'sftp' => 'SFTP', + 'databases' => 'Datenbanken', + 'memo' => 'Memo', + 'created' => 'Erstellt', + 'expires' => 'Läuft ab', + 'public_key' => 'Public key', + 'api_access' => 'Api Access', + 'never' => 'nie', + 'sign_out' => 'Abmelden', + 'admin_control' => 'Admin Control', + 'required' => 'Benötigt', + 'port' => 'Port', + 'username' => 'Username', + 'database' => 'Datenbank', + 'new' => 'Neu', + 'danger' => 'Achtung', + 'create' => 'Erstellen', + 'select_all' => 'Alles auswählen', + 'select_none' => 'Alles abwählen', + 'alias' => 'Alias', + 'primary' => 'Primär', + 'make_primary' => 'Primär machen', + 'none' => 'Nichts', + 'cancel' => 'Abbrechen', + 'created_at' => 'Erstellt am', + 'action' => 'Aktion', + 'data' => 'Data', + 'queued' => 'Queued', + 'last_run' => 'Letzte Ausführung', + 'next_run' => 'Nächste Ausführung', + 'not_run_yet' => 'Wurde noch nicht ausgeführt', + 'yes' => 'Ja', + 'no' => 'Nein', + 'delete' => 'Löschen', + '2fa' => '2FA', + 'logout' => 'Abmelden', + 'admin_cp' => 'Admin Control Panel', + 'optional' => 'Optional', + 'read_only' => 'Read Only', + 'relation' => 'Relation', + 'owner' => 'Owner', + 'admin' => 'Admin', + 'subuser' => 'Subuser', + 'captcha_invalid' => 'Der Captcha war ungültig.', + 'tasks' => 'Aufgaben', + 'seconds' => 'Sekunden', + 'minutes' => 'Minuten', + 'days' => [ + 'sun' => 'Sontag', + 'mon' => 'Montag', + 'tues' => 'Dienstag', + 'wed' => 'Mittwoch', + 'thurs' => 'Donnerstag', + 'fri' => 'Freitag', + 'sat' => 'Samstag', + ], +]; diff --git a/resources/lang/en/admin/nests.php b/resources/lang/en/admin/nests.php new file mode 100644 index 000000000..19e6b5a1e --- /dev/null +++ b/resources/lang/en/admin/nests.php @@ -0,0 +1,33 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'notices' => [ + 'created' => 'A new nest, :name, has been successfully created.', + 'deleted' => 'Successfully deleted the requested nest from the Panel.', + 'updated' => 'Successfully updated the nest configuration options.', + ], + 'eggs' => [ + 'notices' => [ + 'imported' => 'Successfully imported this Egg and its associated variables.', + 'updated_via_import' => 'This Egg has been updated using the file provided.', + 'deleted' => 'Successfully deleted the requested egg from the Panel.', + 'updated' => 'Egg configuration has been updated successfully.', + 'script_updated' => 'Egg install script has been updated and will run whenever servers are installed.', + 'egg_created' => 'A new egg was laid successfully. You will need to restart any running daemons to apply this new egg.', + ], + ], + 'variables' => [ + 'notices' => [ + 'variable_deleted' => 'The variable ":variable" has been deleted and will no longer be available to servers once rebuilt.', + 'variable_updated' => 'The variable ":variable" has been updated. You will need to rebuild any servers using this variable in order to apply changes.', + 'variable_created' => 'New variable has successfully been created and assigned to this egg.', + ], + ], +]; diff --git a/resources/lang/en/admin/node.php b/resources/lang/en/admin/node.php new file mode 100644 index 000000000..5d59e41ce --- /dev/null +++ b/resources/lang/en/admin/node.php @@ -0,0 +1,23 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'validation' => [ + 'fqdn_not_resolvable' => 'The FQDN or IP address provided does not resolve to a valid IP address.', + 'fqdn_required_for_ssl' => 'A fully qualified domain name that resolves to a public IP address is required in order to use SSL for this node.', + ], + 'notices' => [ + 'allocations_added' => 'Allocations have successfully been added to this node.', + 'node_deleted' => 'Node has been successfully removed from the panel.', + 'location_required' => 'You must have at least one location configured before you can add a node to this panel.', + 'node_created' => 'Successfully created new node. You can automatically configure the daemon on this machine by visiting the \'Configuration\' tab. Before you can add any servers you must first allocate at least one IP address and port.', + 'node_updated' => 'Node information has been updated. If any daemon settings were changed you will need to reboot it for those changes to take effect.', + 'unallocated_deleted' => 'Deleted all un-allocated ports for :ip.', + ], +]; diff --git a/resources/lang/en/admin/pack.php b/resources/lang/en/admin/pack.php new file mode 100644 index 000000000..e3a175f62 --- /dev/null +++ b/resources/lang/en/admin/pack.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'notices' => [ + 'pack_updated' => 'Pack has been successfully updated.', + 'pack_deleted' => 'Successfully deleted the pack ":name" from the system.', + 'pack_created' => 'A new pack was successfully created on the system and is now available for deployment to servers.', + ], +]; diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php new file mode 100644 index 000000000..e0ccdba77 --- /dev/null +++ b/resources/lang/en/admin/server.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'exceptions' => [ + 'no_new_default_allocation' => 'You are attempting to delete the default allocation for this server but there is no fallback allocation to use.', + 'marked_as_failed' => 'This server was marked as having failed a previous installation. Current status cannot be toggled in this state.', + 'bad_variable' => 'There was a validation error with the :name variable.', + 'daemon_exception' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', + 'default_allocation_not_found' => 'The requested default allocation was not found in this server\'s allocations.', + ], + 'alerts' => [ + 'startup_changed' => 'The startup configuration for this server has been updated. If this server\'s nest or egg was changed a reinstall will be occuring now.', + 'server_deleted' => 'Server has successfully been deleted from the system.', + 'server_created' => 'Server was successfully created on the panel. Please allow the daemon a few minutes to completely install this server.', + 'build_updated' => 'The build details for this server have been updated. Some changes may require a restart to take effect.', + 'suspension_toggled' => 'Server suspension status has been changed to :status.', + 'rebuild_on_boot' => 'This server has been marked as requiring a Docker Container rebuild. This will happen the next time the server is started.', + 'install_toggled' => 'The installation status for this server has been toggled.', + 'server_reinstalled' => 'This server has been queued for a reinstallation beginning now.', + 'details_updated' => 'Server details have been successfully updated.', + 'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.', + 'node_required' => 'You must have at least one node configured before you can add a server to this panel.', + ], +]; diff --git a/resources/lang/en/admin/user.php b/resources/lang/en/admin/user.php new file mode 100644 index 000000000..b7f756cb4 --- /dev/null +++ b/resources/lang/en/admin/user.php @@ -0,0 +1,18 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'exceptions' => [ + 'user_has_servers' => 'Cannot delete a user with active servers attached to their account. Please delete their servers before continuing.', + ], + 'notices' => [ + 'account_created' => 'Account has been created successfully.', + 'account_updated' => 'Account has been successfully updated.', + ], +]; diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index d7123a809..1abd6bd73 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -3,7 +3,7 @@ return [ 'not_authorized' => 'You are not authorized to perform this action.', 'auth_error' => 'There was an error while attempting to login.', - 'authentication_required' => 'Authentication is required in order to continue.', + 'authentication_required' => 'Authentication is required to continue.', 'remember_me' => 'Remember Me', 'sign_in' => 'Sign In', 'forgot_password' => 'I\'ve forgotten my password!', @@ -13,9 +13,10 @@ return [ 'email_sent' => 'An email has been sent to you with further instructions for resetting your password.', 'failed' => 'The credentials provided to not match those we have on record, or the 2FA token provided was invalid.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - 'password_requirements' => 'Passwords must contain at least one uppercase, lowecase, and numeric character and must be at least 8 characters in length.', + 'password_requirements' => 'Passwords must contain at least one uppercase, lowercase, and numeric character and must be at least 8 characters in length.', 'request_reset' => 'Locate Account', '2fa_required' => '2-Factor Authentication', '2fa_failed' => 'The 2FA token provided was invalid.', 'totp_failed' => 'There was an error while attempting to validate TOTP.', + '2fa_must_be_enabled' => 'The administrator has required that 2-Factor Authentication be enabled for your account in order to use the Panel.', ]; diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 15554fd14..ba63139d3 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -29,184 +29,29 @@ return [ ], 'api' => [ 'index' => [ - 'header' => 'API Access', - 'header_sub' => 'Manage your API access keys.', - 'list' => 'API Keys', + 'list' => 'Your Keys', + 'header' => 'Accout API', + 'header_sub' => 'Manage access keys that allow you to perform actions aganist the panel.', 'create_new' => 'Create New API key', + 'keypair_created' => 'An API key has been successfully generated and is listed below.', ], 'new' => [ 'header' => 'New API Key', - 'header_sub' => 'Create a new API access key', + 'header_sub' => 'Create a new account access key.', 'form_title' => 'Details', 'descriptive_memo' => [ - 'title' => 'Descriptive Memo', - 'description' => 'Enter a brief description of what this API key will be used for.', + 'title' => 'Description', + 'description' => 'Enter a brief description of this key that will be useful for reference.', ], 'allowed_ips' => [ 'title' => 'Allowed IPs', 'description' => 'Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.', ], ], - 'permissions' => [ - 'user' => [ - 'server_header' => 'User Server Permissions', - 'server' => [ - 'list' => [ - 'title' => 'List Servers', - 'desc' => 'Allows listing of all servers a user owns or has access to as a subuser.', - ], - 'view' => [ - 'title' => 'View Server', - 'desc' => 'Allows viewing of specific server user can access.', - ], - 'power' => [ - 'title' => 'Toggle Power', - 'desc' => 'Allow toggling of power status for a server.', - ], - 'command' => [ - 'title' => 'Send Command', - 'desc' => 'Allow sending of a command to a running server.', - ], - ], - ], - 'admin' => [ - 'server_header' => 'Server Control', - 'server' => [ - 'list' => [ - 'title' => 'List Servers', - 'desc' => 'Allows listing of all servers currently on the system.', - ], - 'view' => [ - 'title' => 'View Server', - 'desc' => 'Allows view of single server including service and details.', - ], - 'delete' => [ - 'title' => 'Delete Server', - 'desc' => 'Allows deletion of a server from the system.', - ], - 'create' => [ - 'title' => 'Create Server', - 'desc' => 'Allows creation of a new server on the system.', - ], - 'edit-details' => [ - 'title' => 'Edit Server Details', - 'desc' => 'Allows editing of server details such as name, owner, description, and secret key.', - ], - 'edit-container' => [ - 'title' => 'Edit Server Container', - 'desc' => 'Allows for modification of the docker container the server runs in.', - ], - 'suspend' => [ - 'title' => 'Suspend Server', - 'desc' => 'Allows for the suspension and unsuspension of a given server.', - ], - 'install' => [ - 'title' => 'Toggle Install Status', - 'desc' => '', - ], - 'rebuild' => [ - 'title' => 'Rebuild Server', - 'desc' => '', - ], - 'edit-build' => [ - 'title' => 'Edit Server Build', - 'desc' => 'Allows editing of server build setting such as CPU and memory allocations.', - ], - 'edit-startup' => [ - 'title' => 'Edit Server Startup', - 'desc' => 'Allows modification of server startup commands and parameters.', - ], - ], - 'location_header' => 'Location Control', - 'location' => [ - 'list' => [ - 'title' => 'List Locations', - 'desc' => 'Allows listing all locations and thier associated nodes.', - ], - ], - 'node_header' => 'Node Control', - 'node' => [ - 'list' => [ - 'title' => 'List Nodes', - 'desc' => 'Allows listing of all nodes currently on the system.', - ], - 'view' => [ - 'title' => 'View Node', - 'desc' => 'Allows viewing details about a specific node including active services.', - ], - 'view-config' => [ - 'title' => 'View Node Configuration', - 'desc' => 'Danger. This allows the viewing of the node configuration file used by the daemon, and exposes secret daemon tokens.', - ], - 'create' => [ - 'title' => 'Create Node', - 'desc' => 'Allows creating a new node on the system.', - ], - 'delete' => [ - 'title' => 'Delete Node', - 'desc' => 'Allows deletion of a node from the system.', - ], - ], - 'user_header' => 'User Control', - 'user' => [ - 'list' => [ - 'title' => 'List Users', - 'desc' => 'Allows listing of all users currently on the system.', - ], - 'view' => [ - 'title' => 'View User', - 'desc' => 'Allows viewing details about a specific user including active services.', - ], - 'create' => [ - 'title' => 'Create User', - 'desc' => 'Allows creating a new user on the system.', - ], - 'edit' => [ - 'title' => 'Update User', - 'desc' => 'Allows modification of user details.', - ], - 'delete' => [ - 'title' => 'Delete User', - 'desc' => 'Allows deleting a user.', - ], - ], - 'service_header' => 'Service Control', - 'service' => [ - 'list' => [ - 'title' => 'List Services', - 'desc' => 'Allows listing of all services configured on the system.', - ], - 'view' => [ - 'title' => 'View Service', - 'desc' => 'Allows listing details about each service on the system including service options and variables.', - ], - ], - 'option_header' => 'Option Control', - 'option' => [ - 'list' => [ - 'title' => 'List Options', - 'desc' => '', - ], - 'view' => [ - 'title' => 'View Option', - 'desc' => '', - ], - ], - 'pack_header' => 'Pack Control', - 'pack' => [ - 'list' => [ - 'title' => 'List Packs', - 'desc' => '', - ], - 'view' => [ - 'title' => 'View Pack', - 'desc' => '', - ], - ], - ], - ], ], 'account' => [ + 'details_updated' => 'Your account details have been successfully updated.', + 'invalid_password' => 'The password provided for your account was not valid.', 'header' => 'Your Account', 'header_sub' => 'Manage your account details.', 'update_pass' => 'Update Password', @@ -219,10 +64,9 @@ return [ 'last_name' => 'Last Name', 'update_identitity' => 'Update Identity', 'username_help' => 'Your username must be unique to your account, and may only contain the following characters: :requirements.', - 'invalid_pass' => 'The password provided was not valid for this account.', - 'exception' => 'An error occurred while attempting to update your account.', ], 'security' => [ + 'session_mgmt_disabled' => 'Your host has not enabled the ability to manage account sessions via this interface.', 'header' => 'Account Security', 'header_sub' => 'Control active sessions and 2-Factor Authentication.', 'sessions' => 'Active Sessions', @@ -234,5 +78,6 @@ return [ 'enable_2fa' => 'Enable 2-Factor Authentication', '2fa_qr' => 'Confgure 2FA on Your Device', '2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.', + '2fa_disable_error' => 'The 2FA token provided was not valid. Protection has not been disabled for this account.', ], ]; diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php new file mode 100644 index 000000000..77f67c663 --- /dev/null +++ b/resources/lang/en/command/messages.php @@ -0,0 +1,88 @@ + [ + '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, UUID, 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', + ], + 'environment' => [ + 'mail' => [ + 'ask_smtp_host' => 'SMTP Host (e.g. smtp.google.com)', + 'ask_smtp_port' => 'SMTP Port', + 'ask_smtp_username' => 'SMTP Username', + 'ask_smtp_password' => 'SMTP Password', + 'ask_mailgun_domain' => 'Mailgun Domain', + '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 inaccessable 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?', + ], + ], +]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php new file mode 100644 index 000000000..73b910d0d --- /dev/null +++ b/resources/lang/en/exceptions.php @@ -0,0 +1,66 @@ + 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', + 'node' => [ + 'servers_attached' => 'A node must have no servers linked to it in order to be deleted.', + 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (core.json) for the daemon to apply these changes.', + ], + 'allocations' => [ + 'server_using' => 'A server is currently assigned to this allocation. An allocation can only be deleted if no server is currently assigned.', + 'too_many_ports' => 'Adding more than 1000 ports at a single time is not supported. Please use a smaller range.', + 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', + 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', + ], + 'nest' => [ + 'delete_has_servers' => 'A Nest with active servers attached to it cannot be deleted from the Panel.', + 'egg' => [ + 'delete_has_servers' => 'An Egg with active servers attached to it cannot be deleted from the Panel.', + 'invalid_copy_id' => 'The Egg selected for copying a script from either does not exist, or is copying a script itself.', + 'must_be_child' => 'The "Copy Settings From" directive for this Egg must be a child option for the selected Nest.', + 'has_children' => 'This Egg is a parent to one or more other Eggs. Please delete those Eggs before deleting this Egg.', + ], + 'variables' => [ + 'env_not_unique' => 'The environment variable :name must be unique to this Egg.', + 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', + ], + 'importer' => [ + 'json_error' => 'There was an error while attempting to parse the JSON file: :error.', + 'file_error' => 'The JSON file provided was not valid.', + 'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.', + ], + ], + 'packs' => [ + 'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.', + 'update_has_servers' => 'Cannot modify the associated option ID when servers are currently attached to a pack.', + 'invalid_upload' => 'The file provided does not appear to be valid.', + 'invalid_mime' => 'The file provided does not meet the required type :type', + 'unreadable' => 'The archive provided could not be opened by the server.', + 'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.', + 'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.', + ], + 'subusers' => [ + 'editing_self' => 'Editing your own subuser account is not permitted.', + 'user_is_owner' => 'You cannot add the server owner as a subuser for this server.', + 'subuser_exists' => 'A user with that email address is already assigned as a subuser for this server.', + ], + 'databases' => [ + 'delete_has_databases' => 'Cannot delete a database host server that has active databases linked to it.', + ], + 'tasks' => [ + 'chain_interval_too_long' => 'The maximum interval time for a chained task is 15 minutes.', + ], + 'locations' => [ + 'has_nodes' => 'Cannot delete a location that has active nodes attached to it.', + ], + 'users' => [ + 'node_revocation_failed' => 'Failed to revoke keys on Node #:node. :error', + ], + 'deployment' => [ + 'no_viable_nodes' => 'No nodes satisfying the requirements specified for automatic deployment could be found.', + 'no_viable_allocations' => 'No allocations satisfying the requirements for automatic deployment were found.', + ], + 'api' => [ + 'resource_not_found' => 'The requested resource does not exist on this server.', + ], +]; diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php index a8d271017..f8a9deebb 100644 --- a/resources/lang/en/navigation.php +++ b/resources/lang/en/navigation.php @@ -6,7 +6,7 @@ return [ 'header' => 'ACCOUNT MANAGEMENT', 'my_account' => 'My Account', 'security_controls' => 'Security Controls', - 'api_access' => 'API Access', + 'api_access' => 'Account API', 'my_servers' => 'My Servers', ], 'server' => [ @@ -18,12 +18,14 @@ return [ 'create_file' => 'Create File', 'upload_files' => 'Upload Files', 'subusers' => 'Subusers', - 'task_management' => 'Task Management', + 'schedules' => 'Schedules', 'configuration' => 'Configuration', - 'port_allocations' => 'Port Allocations', + 'port_allocations' => 'Allocation Settings', 'sftp_settings' => 'SFTP Settings', 'startup_parameters' => 'Startup Parameters', 'databases' => 'Databases', 'edit_file' => 'Edit File', + 'admin_header' => 'ADMINISTRATIVE', + 'admin' => 'Server Configuration', ], ]; diff --git a/resources/lang/en/pagination.php b/resources/lang/en/pagination.php index fcab34b25..ecac3aa33 100644 --- a/resources/lang/en/pagination.php +++ b/resources/lang/en/pagination.php @@ -1,7 +1,6 @@ '« Previous', - 'next' => 'Next »', - + 'next' => 'Next »', ]; diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index 791f1770a..0d266835a 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -6,7 +6,46 @@ return [ 'header' => 'Server Console', 'header_sub' => 'Control your server in real time.', ], + 'schedule' => [ + 'header' => 'Schedule Manager', + 'header_sub' => 'Manage all of this server\'s schedules in one place.', + 'current' => 'Current Schedules', + 'new' => [ + 'header' => 'Create New Schedule', + 'header_sub' => 'Create a new set of scheduled tasks for this server.', + 'submit' => 'Create Schedule', + ], + 'manage' => [ + 'header' => 'Manage Schedule', + 'submit' => 'Update Schedule', + 'delete' => 'Delete Schedule', + ], + 'task' => [ + 'time' => 'After', + 'action' => 'Perform Action', + 'payload' => 'With Payload', + 'add_more' => 'Add Another Task', + ], + 'actions' => [ + 'command' => 'Send Command', + 'power' => 'Power Action', + ], + 'toggle' => 'Toggle Status', + 'run_now' => 'Trigger Schedule', + 'schedule_created' => 'Successfully created a new schedule for this server.', + 'schedule_updated' => 'Schedule has been updated.', + 'unnamed' => 'Unnamed Schedule', + 'setup' => 'Schedule Setup', + 'day_of_week' => 'Day of Week', + 'day_of_month' => 'Day of Month', + 'hour' => 'Hour of Day', + 'minute' => 'Minute of Hour', + 'time_help' => 'The schedule system supports the use of Cronjob syntax when defining when tasks should begin running. Use the fields above to specify when these tasks should begin running or select options from the multiple select menus.', + 'task_help' => 'Times for tasks are relative to the previously defined task. Each schedule may have no more than 5 tasks assigned to it and tasks may not be scheduled more than 15 minutes apart.', + ], 'tasks' => [ + 'task_created' => 'Successfully created a new task on the Panel.', + 'task_updated' => 'Task has successfully been updated. Any currently queued task actions will be cancelled and run again at the next defined time.', 'header' => 'Scheduled Tasks', 'header_sub' => 'Automate your server.', 'current' => 'Current Scheduled Tasks', @@ -19,6 +58,7 @@ return [ 'new' => [ 'header' => 'New Task', 'header_sub' => 'Create a new scheduled task for this server.', + 'task_name' => 'Task Name', 'day_of_week' => 'Day of Week', 'custom' => 'Custom Value', 'day_of_month' => 'Day of Month', @@ -33,9 +73,16 @@ return [ 'sat' => 'Saturday', 'submit' => 'Create Task', 'type' => 'Task Type', + 'chain_then' => 'Then, After', + 'chain_do' => 'Do', + 'chain_arguments' => 'With Arguments', 'payload' => 'Task Payload', 'payload_help' => 'For example, if you selected Send Command enter the command here. If you selected Send Power Option put the power action here (e.g. restart).', ], + 'edit' => [ + 'header' => 'Manage Task', + 'submit' => 'Update Task', + ], ], 'users' => [ 'header' => 'Manage Users', @@ -44,6 +91,8 @@ return [ 'list' => 'Accounts with Access', 'add' => 'Add New Subuser', 'update' => 'Update Subuser', + 'user_assigned' => 'Successfully assigned a new subuser to this server.', + 'user_updated' => 'Successfully updated permissions.', 'edit' => [ 'header' => 'Edit Subuser', 'header_sub' => 'Modify user\'s access to server.', @@ -57,8 +106,7 @@ return [ 'file_header' => 'File Management', 'subuser_header' => 'Subuser Management', 'server_header' => 'Server Management', - 'task_header' => 'Task Management', - 'sftp_header' => 'SFTP Management', + 'task_header' => 'Schedule Management', 'database_header' => 'Database Management', 'power_start' => [ 'title' => 'Start Server', @@ -80,17 +128,21 @@ return [ 'title' => 'Send Console Command', 'description' => 'Allows sending a command from the console. If the user does not have stop or restart permissions they cannot send the application\'s stop command.', ], + 'view_sftp' => [ + 'title' => 'SFTP Allowed', + 'description' => 'Allows user to connect to the SFTP server provided by the daemon.', + ], 'list_files' => [ 'title' => 'List Files', 'description' => 'Allows user to list all files and folders on the server but not view file contents.', ], 'edit_files' => [ 'title' => 'Edit Files', - 'description' => 'Allows user to open a file for viewing only.', + 'description' => 'Allows user to open a file for viewing only. SFTP is not effected by this permission.', ], 'save_files' => [ 'title' => 'Save Files', - 'description' => 'Allows user to save modified file contents.', + 'description' => 'Allows user to save modified file contents. SFTP is not effected by this permission.', ], 'move_files' => [ 'title' => 'Rename & Move Files', @@ -144,9 +196,13 @@ return [ 'title' => 'Delete Subuser', 'description' => 'Allows a user to delete other subusers on the server.', ], - 'set_connection' => [ - 'title' => 'Set Default Connection', - 'description' => 'Allows user to set the default connection used for a server as well as view avaliable ports.', + 'view_allocations' => [ + 'title' => 'View Allocations', + 'description' => 'Allows user to view all of the IPs and ports assigned to a server.', + ], + 'edit_allocation' => [ + 'title' => 'Edit Default Connection', + 'description' => 'Allows user to change the default connection allocation to use for a server.', ], 'view_startup' => [ 'title' => 'View Startup Command', @@ -156,41 +212,33 @@ return [ 'title' => 'Edit Startup Command', 'description' => 'Allows a user to modify startup variables for a server.', ], - 'list_tasks' => [ - 'title' => 'List Tasks', - 'description' => 'Allows a user to list all tasks (enabled and disabled) on a server.', + 'list_schedules' => [ + 'title' => 'List Schedules', + 'description' => 'Allows a user to list all schedules (enabled and disabled) for this server.', ], - 'view_task' => [ - 'title' => 'View Task', - 'description' => 'Allows a user to view a specific task\'s details.', + 'view_schedule' => [ + 'title' => 'View Schedule', + 'description' => 'Allows a user to view a specific schedule\'s details including all of the assigned tasks.', ], - 'toggle_task' => [ - 'title' => 'Toggle Task', - 'description' => 'Allows a user to toggle a task on or off.', + 'toggle_schedule' => [ + 'title' => 'Toggle Schedule', + 'description' => 'Allows a user to toggle a schedule to be active or inactive.', ], - 'queue_task' => [ - 'title' => 'Queue Task', - 'description' => 'Allows a user to queue a task to run on next cycle.', + 'queue_schedule' => [ + 'title' => 'Queue Schedule', + 'description' => 'Allows a user to queue a schedule to run it\'s tasks on the next process cycle.', ], - 'create_task' => [ - 'title' => 'Create Task', - 'description' => 'Allows a user to create new tasks.', + 'edit_schedule' => [ + 'title' => 'Edit Schedule', + 'description' => 'Allows a user to edit a schedule including all of the schedule\'s tasks. This will allow the user to remove individual tasks, but not delete the schedule itself.', ], - 'delete_task' => [ - 'title' => 'Delete Task', - 'description' => 'Allows a user to delete a task.', + 'create_schedule' => [ + 'title' => 'Create Schedule', + 'description' => 'Allows a user to create a new schedule.', ], - 'view_sftp' => [ - 'title' => 'View SFTP Details', - 'description' => 'Allows user to view the server\'s SFTP information but not the password.', - ], - 'view_sftp_password' => [ - 'title' => 'View SFTP Password', - 'description' => 'Allows user to view the SFTP password for the server.', - ], - 'reset_sftp' => [ - 'title' => 'Reset SFTP Password', - 'description' => 'Allows user to change the SFTP password for the server.', + 'delete_schedule' => [ + 'title' => 'Delete Schedule', + 'description' => 'Allows a user to delete a schedule from the server.', ], 'view_databases' => [ 'title' => 'View Database Details', @@ -203,6 +251,10 @@ return [ ], ], 'files' => [ + 'exceptions' => [ + 'invalid_mime' => 'This type of file cannot be edited via the Panel\'s built-in editor.', + 'max_size' => 'This file is too large to edit via the Panel\'s built-in editor.', + ], 'header' => 'File Manager', 'header_sub' => 'Manage all of your files directly from the web.', 'loading' => 'Loading initial file structure, this could take a few seconds.', @@ -213,6 +265,8 @@ return [ 'last_modified' => 'Last Modified', 'add_new' => 'Add New File', 'add_folder' => 'Add New Folder', + 'mass_actions' => 'Mass actions', + 'delete' => 'Delete', 'edit' => [ 'header' => 'Edit File', 'header_sub' => 'Make modifications to a file from the web.', @@ -233,21 +287,20 @@ return [ 'command' => 'Startup Command', 'edit_params' => 'Edit Parameters', 'update' => 'Update Startup Parameters', - 'startup_var' => 'Startup Command Variable', 'startup_regex' => 'Input Rules', + 'edited' => 'Startup variables have been successfully edited. They will take effect the next time this server is started.', ], 'sftp' => [ 'header' => 'SFTP Configuration', 'header_sub' => 'Account details for SFTP connections.', - 'change_pass' => 'Change SFTP Password', 'details' => 'SFTP Details', 'conn_addr' => 'Connection Address', - 'warning' => 'Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', + 'warning' => 'The SFTP password is your account password. Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', ], 'database' => [ 'header' => 'Databases', 'header_sub' => 'All databases available for this server.', - 'your_dbs' => 'Your Databases', + 'your_dbs' => 'Configured Databases', 'host' => 'MySQL Host', 'reset_password' => 'Reset Password', 'no_dbs' => 'There are no databases listed for this server.', diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index 864cdf2e2..5b9173866 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -32,7 +32,7 @@ return [ 'memo' => 'Memo', 'created' => 'Created', 'expires' => 'Expires', - 'public_key' => 'Public key', + 'public_key' => 'Token', 'api_access' => 'Api Access', 'never' => 'never', 'sign_out' => 'Sign out', @@ -71,4 +71,17 @@ return [ 'admin' => 'Admin', 'subuser' => 'Subuser', 'captcha_invalid' => 'The provided captcha is invalid.', + 'tasks' => 'Tasks', + 'seconds' => 'Seconds', + 'minutes' => 'Minutes', + 'days' => [ + 'sun' => 'Sunday', + 'mon' => 'Monday', + 'tues' => 'Tuesday', + 'wed' => 'Wednesday', + 'thurs' => 'Thursday', + 'fri' => 'Friday', + 'sat' => 'Saturday', + ], + 'last_used' => 'Last Used', ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 9608bc25b..201880ec9 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -1,7 +1,6 @@ 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'before' => 'The :attribute must be a date before :date.', - 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', - 'between' => [ + 'accepted' => 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'email' => 'The :attribute must be a valid email address.', - 'exists' => 'The selected :attribute is invalid.', - 'file' => 'The :attribute must be a file.', - 'filled' => 'The :attribute field is required.', - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'max' => [ + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field is required.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', ], - 'mimes' => 'The :attribute must be a file of type: :values.', - 'mimetypes' => 'The :attribute must be a file of type: :values.', - 'min' => [ + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', ], - 'not_in' => 'The selected :attribute is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'present' => 'The :attribute field must be present.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', + 'not_in' => 'The selected :attribute is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'size' => [ + 'same' => 'The :attribute and :other must match.', + 'size' => [ 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', - ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'unique' => 'The :attribute has already been taken.', - 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'custom-message', - ], + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', ], + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', /* |-------------------------------------------------------------------------- @@ -116,4 +98,8 @@ return [ 'attributes' => [], + // Internal validation logic for Pterodactyl + 'internal' => [ + 'variable_value' => ':env variable', + ], ]; diff --git a/resources/lang/es/admin/nests.php b/resources/lang/es/admin/nests.php new file mode 100644 index 000000000..83cb0c430 --- /dev/null +++ b/resources/lang/es/admin/nests.php @@ -0,0 +1,32 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'notices' => [ + 'service_created' => 'Un nuevo nido, a :name, ha sido creado con éxito.', + 'service_deleted' => 'Se ha eliminado correctamente el nido solicitado del panel.', + 'service_updated' => 'Se actualizaron correctamente las opciones de configuración del nido.', + 'functions_updated' => 'Se ha actualizado el archivo de funciones de nido. Tendrá que reiniciar los nodos para que estos cambios se apliquen.', + ], + 'options' => [ + 'notices' => [ + 'option_deleted' => 'Se ha eliminado correctamente la opción del huevo solicitada del Panel.', + 'option_updated' => 'Opción del huevo se ha actualizado correctamente.', + 'script_updated' => 'Opción del huevo de script de instalación se ha actualizado y se ejecutará cuando se instalan servidores.', + 'option_created' => 'Nueva opción del huevo se ha creado correctamente. Es necesario reiniciar los demonios ejecutándose para aplicar este nuevo huevo.', + ], + ], + 'variables' => [ + 'notices' => [ + 'variable_deleted' => 'La variable ":variable" se ha eliminado y ya no estará disponible para los servidores una vez reconstruida.', + 'variable_updated' => 'Se ha actualizado la variable ":variable". Es necesario reconstruir los servidores que utilizan esta variable con el fin de aplicar los cambios.', + 'variable_created' => 'Nueva variable de éxito ha sido creado y asignado a esta opción del huevo.', + ], + ], +]; diff --git a/resources/lang/es/admin/node.php b/resources/lang/es/admin/node.php new file mode 100644 index 000000000..7a5c70e12 --- /dev/null +++ b/resources/lang/es/admin/node.php @@ -0,0 +1,23 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'validation' => [ + 'fqdn_not_resolvable' => 'El FQDN o la dirección IP proporcionada no se resuelve a una dirección IP válida.', + 'fqdn_required_for_ssl' => 'Se requiere de la onu FQDN que resuelva una dirección IP pública para poder usar SSL para este nodo.', + ], + 'notices' => [ + 'allocations_added' => 'Las asignaciones se han agregado con éxito un este nodo.', + 'node_deleted' => 'Nodo se ha eliminado con éxito desde el panel de.', + 'location_required' => 'Necesita al menos una ubicación configurada antes de poder agregar la onu un nodo este panel.', + 'node_created' => 'Creado con éxito nuevo nodo. Puede configurar automáticamente el daemon en esta máquina visitando la pestaña \'Configuración\'. Antes de que usted puede agregar cualquier cantidad de servidores primero debe asignar al menos una dirección IP y el puerto.', + 'node_updated' => 'La información del nodo se ha actualizado. Si se ha cambiado la configuración del demonio, deberá reiniciarlo para que los cambios surtan efecto.', + 'unallocated_deleted' => 'Se eliminaron todos los puertos asignados para :ip.', + ], +]; diff --git a/resources/lang/es/admin/pack.php b/resources/lang/es/admin/pack.php new file mode 100644 index 000000000..6940ec3b9 --- /dev/null +++ b/resources/lang/es/admin/pack.php @@ -0,0 +1,16 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'notices' => [ + 'pack_updated' => 'El paquete se ha actualizado correctamente.', + 'pack_deleted' => 'Eliminado correctamente el paquete ":name" del sistema.', + 'pack_created' => 'Un nuevo paquete se creó con éxito en el sistema y ahora está disponible para la implementación en los servidores.', + ], +]; diff --git a/resources/lang/es/admin/server.php b/resources/lang/es/admin/server.php new file mode 100644 index 000000000..0b77f44d1 --- /dev/null +++ b/resources/lang/es/admin/server.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'exceptions' => [ + 'no_new_default_allocation' => 'Está intentando eliminar la asignación predeterminada para este servidor, pero no hay una asignación alternativa para usar.', + 'marked_as_failed' => 'Este servidor fue marcado como que falló una instalación previa. El estado actual no se puede cambiar en este estado.', + 'bad_variable' => 'Hubo un error de validación con la variable: name.', + 'daemon_exception' => 'Hubo una excepción al intentar comunicarse con el daemon que dio como resultado un código de respuesta HTTP /:code. Esta excepción ha sido registrada.', + 'default_allocation_not_found' => 'La asignación predeterminada solicitada no se encontró en las asignaciones de este servidor.', + ], + 'alerts' => [ + 'startup_changed' => 'Se ha actualizado la configuración de inicio de este servidor. Si se ha cambiado el servicio o la opción de este servidor una reinstalación será ocurriendo ahora.', + 'server_deleted' => 'Se ha eliminado correctamente el servidor.', + 'server_created' => 'El servidor se creó correctamente en el panel. Por favor, permite que el demonio de unos pocos minutos para instalar por completo este servidor.', + 'build_updated' => 'Los detalles de la compilación para este servidor se han actualizado. Algunos cambios pueden requerir un reinicio para tener efecto.', + 'suspension_toggled' => 'El estado de la suspensión del servidor se ha cambiado a :status.', + 'rebuild_on_boot' => 'Este servidor se ha marcado como que requiere una reconstrucción de Contenedor Docker. El servidor se reconstruirá la próxima vez que se inicie.', + 'install_toggled' => 'Se ha cambiado el estado de la instalación de este servidor.', + 'server_reinstalled' => 'Este servidor se ha puesto en cola para una reinstalación que comenzar ahora.', + 'details_updated' => 'Los detalles del servidor se han actualizado correctamente.', + 'docker_image_updated' => 'Cambió correctamente la imagen predeterminada de Docker para utilizarla en este servidor. Se requiere un reinicio para aplicar este cambio.', + 'node_required' => 'Debe tener al menos un nodo configurado antes de poder agregar un servidor a este panel.', + ], +]; diff --git a/resources/lang/es/admin/user.php b/resources/lang/es/admin/user.php new file mode 100644 index 000000000..066652e2a --- /dev/null +++ b/resources/lang/es/admin/user.php @@ -0,0 +1,18 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'exceptions' => [ + 'user_has_servers' => 'No se puede eliminar un usuario con servidores activos conectados a su cuenta. Elimine los servidores antes de continuar.', + ], + 'notices' => [ + 'account_created' => 'La cuenta se ha creado correctamente.', + 'account_updated' => 'La cuenta se ha actualizado correctamente.', + ], +]; diff --git a/resources/lang/es/auth.php b/resources/lang/es/auth.php new file mode 100644 index 000000000..e8269476c --- /dev/null +++ b/resources/lang/es/auth.php @@ -0,0 +1,21 @@ + 'No está autorizado a utilizar esta acción.', + 'auth_error' => 'Se ha producido un error al intentar iniciar sesión.', + 'authentication_required' => 'La autenticación es necesaria para continuar.', + 'remember_me' => 'Recuérdame', + 'sign_in' => 'Iniciar Sesión', + 'forgot_password' => '¡Olvidé mi contraseña!', + 'request_reset_text' => '¿Olvidaste tu contraseña? No es el fin del mundo, sólo proporcione su correo electrónico a continuación.', + 'reset_password_text' => 'Restablece la contraseña de su cuenta.', + 'reset_password' => 'Restablece contraseña de cuenta.', + 'email_sent' => 'Se le ha enviado un correo electrónico con instrucciones adicionales para restablecer su contraseña.', + 'failed' => 'Las credenciales proporcionadas a no coinciden con los que tenemos, o el token 2FA proporcionado no es válido.', + 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor, inténtelo de nuevo en :seconds segundos.', + 'password_requirements' => 'Su contraseña debe contener al menos un carácter en mayúsculas, minúsculas y numérico y debe tener al menos 8 caracteres de longitud.', + 'request_reset' => 'Localiza su cuenta.', + '2fa_required' => '2-Factor Autenticación', + '2fa_failed' => 'El token 2FA proporcionado no es válido.', + 'totp_failed' => 'Hubo un error al intentar validar TOTP.', +]; diff --git a/resources/lang/es/base.php b/resources/lang/es/base.php new file mode 100644 index 000000000..0593be075 --- /dev/null +++ b/resources/lang/es/base.php @@ -0,0 +1,241 @@ + 'Hubo un error con uno o más campos en la solicitud.', + 'errors' => [ + 'return' => 'Regresar a la Página Anterior', + 'home' => 'Ir A Casa', + '403' => [ + 'header' => 'Prohibido', + 'desc' => 'Usted no tiene permiso para acceder a este recurso en este servidor.', + ], + '404' => [ + 'header' => 'No Se Encuentra El Archivo', + 'desc' => 'No hemos podido localizar el recurso solicitado en el servidor.', + ], + 'installing' => [ + 'header' => 'El Servidor De Instalación', + 'desc' => 'El servidor solicitado aún no ha finalizado el proceso de instalación. Por favor, vuelva en unos pocos minutos, usted debe recibir un correo electrónico tan pronto como este proceso se haya completado.', + ], + 'suspended' => [ + 'header' => 'Servidor Suspendido', + 'desc' => 'Este servidor ha sido suspendido y no se puede acceder.', + ], + ], + 'index' => [ + 'header' => 'Sus Servidores', + 'header_sub' => 'Los servidores que tienen acceso a.', + 'list' => 'Lista De Servidor', + ], + 'api' => [ + 'index' => [ + 'header' => 'El Acceso a la API', + 'header_sub' => 'Gestionar su acceso a la API de teclas.', + 'list' => 'Claves de API', + 'create_new' => 'Crear Nueva clave de API', + 'keypair_created' => 'Una API Key-Pair se ha generado. Su API token secreto es :token. Por favor, tome nota de esta clave como no se mostrará de nuevo.', + ], + 'new' => [ + 'header' => 'Nueva Clave de API', + 'header_sub' => 'Crear una API nueva clave de acceso', + 'form_title' => 'Detalles', + 'descriptive_memo' => [ + 'title' => 'Descriptivo Memo', + 'description' => 'Escriba una breve descripción de lo que esta clave de API se utiliza para.', + ], + 'allowed_ips' => [ + 'title' => 'IPs Permitidas', + 'description' => 'Escriba una línea acotada lista de IPs que tienen permitido el acceso a la API usando esta clave. La notación CIDR es permitido. Dejar en blanco para permitir que cualquier IP.', + ], + ], + 'permissions' => [ + 'user' => [ + 'server_header' => 'Usuario Permisos De Servidor', + 'server' => [ + 'list' => [ + 'title' => 'Lista De Los Servidores', + 'desc' => 'Permite listado de todos los servidores de un usuario posee o tiene acceso a un subuser.', + ], + 'view' => [ + 'title' => 'Vista Del Servidor', + 'desc' => 'Permite la visualización de servidor específico de usuario puede tener acceso a.', + ], + 'power' => [ + 'title' => 'Alternar El Poder', + 'desc' => 'Permitir la activación o desactivación de estado de energía para un servidor.', + ], + 'command' => [ + 'title' => 'Enviar Comando', + 'desc' => 'Permitir el envío de un comando a un servidor en ejecución.', + ], + ], + ], + 'admin' => [ + 'server_header' => 'Control De Servidor', + 'server' => [ + 'list' => [ + 'title' => 'Lista De Los Servidores', + 'desc' => 'Permite listado de todos los servidores en la actualidad en el sistema de.', + ], + 'view' => [ + 'title' => 'Vista Del Servidor', + 'desc' => 'Permite ver de un solo servidor, incluyendo los de servicio y los detalles.', + ], + 'delete' => [ + 'title' => 'Eliminar Servidor', + 'desc' => 'Permite la eliminación de un servidor del sistema.', + ], + 'create' => [ + 'title' => 'Crear Servidor', + 'desc' => 'Permite la creación de un nuevo servidor en el sistema.', + ], + 'edit-details' => [ + 'title' => 'Editar Los Detalles Del Servidor De', + 'desc' => 'Permite la edición de los datos del servidor, tales como nombre, propietario, descripción y clave secreta.', + ], + 'edit-container' => [ + 'title' => 'Editar Servidor De Contenedor', + 'desc' => 'Permite la modificación de la ventana acoplable contenedor el servidor se ejecuta en.', + ], + 'suspend' => [ + 'title' => 'Suspender Servidor', + 'desc' => 'Permite la suspensión y unsuspension de un determinado servidor.', + ], + 'install' => [ + 'title' => 'Alternar El Estado De Instalación', + 'desc' => '', + ], + 'rebuild' => [ + 'title' => 'Reconstruir Servidor', + 'desc' => '', + ], + 'edit-build' => [ + 'title' => 'Edición De Compilación Del Servidor', + 'desc' => 'Permite la edición de compilación del servidor de configuración de la CPU y de la memoria de las asignaciones.', + ], + 'edit-startup' => [ + 'title' => 'Editar El Inicio Del Servidor', + 'desc' => 'Permite la modificación de servidor de comandos de inicio y los parámetros de.', + ], + ], + 'location_header' => 'Control De Ubicación De', + 'location' => [ + 'list' => [ + 'title' => 'Lista De Ubicaciones', + 'desc' => 'Permite listado de todos los lugares y sus nodos asociados.', + ], + ], + 'node_header' => 'Nodo De Control', + 'node' => [ + 'list' => [ + 'title' => 'Lista De Nodos', + 'desc' => 'Permite listado de todos los nodos en la actualidad en el sistema de.', + ], + 'view' => [ + 'title' => 'Nodo Vista', + 'desc' => 'Permite ver los detalles acerca de un determinado nodo, incluyendo los servicios activos.', + ], + 'view-config' => [ + 'title' => 'Vista De Configuración De Nodo', + 'desc' => 'Peligro. Esto permite la visualización de la configuración del nodo de archivo utilizado por el demonio, y expone secreto demonio tokens.', + ], + 'create' => [ + 'title' => 'Crear Nodo', + 'desc' => 'Permite la creación de un nuevo nodo en el sistema.', + ], + 'delete' => [ + 'title' => 'Eliminar El Nodo', + 'desc' => 'Permite la eliminación de un nodo del sistema.', + ], + ], + 'user_header' => 'Control De Usuario', + 'user' => [ + 'list' => [ + 'title' => 'Los Usuarios De La Lista', + 'desc' => 'Permite listado de todos los usuarios de la actualidad en el sistema de.', + ], + 'view' => [ + 'title' => 'Vista De Usuario', + 'desc' => 'Permite ver los detalles acerca de un usuario específico, incluyendo los servicios activos.', + ], + 'create' => [ + 'title' => 'Crear Usuario', + 'desc' => 'Permite crear un nuevo usuario en el sistema.', + ], + 'edit' => [ + 'title' => 'Actualización De Usuario', + 'desc' => 'Permite la modificación de datos del usuario.', + ], + 'delete' => [ + 'title' => 'Eliminar Usuario', + 'desc' => 'Permite la eliminación de un usuario.', + ], + ], + 'service_header' => 'Servicio De Control De', + 'service' => [ + 'list' => [ + 'title' => 'Servicio De Lista De', + 'desc' => 'Permite listado de todos los servicios configurados en el sistema.', + ], + 'view' => [ + 'title' => 'Ver Servicio', + 'desc' => 'Permite el listado de más detalles acerca de cada servicio en el sistema, incluyendo las opciones de servicio y variables.', + ], + ], + 'option_header' => 'Opción De Control', + 'option' => [ + 'list' => [ + 'title' => 'Opciones De La Lista De', + 'desc' => '', + ], + 'view' => [ + 'title' => 'La Opción De Vista', + 'desc' => '', + ], + ], + 'pack_header' => 'Pack De Control De', + 'pack' => [ + 'list' => [ + 'title' => 'Lista De Paquetes De', + 'desc' => '', + ], + 'view' => [ + 'title' => 'Vista Pack', + 'desc' => '', + ], + ], + ], + ], + ], + 'account' => [ + 'details_updated' => 'Los detalles de su cuenta se han actualizado correctamente.', + 'invalid_password' => 'La contraseña proporcionada por su cuenta no era válido.', + 'header' => 'Su Cuenta', + 'header_sub' => 'Gestionar los detalles de su cuenta.', + 'update_pass' => 'Actualización De Contraseña', + 'update_email' => 'Actualización De La Dirección De Correo Electrónico', + 'current_password' => 'Contraseña Actual', + 'new_password' => 'Nueva Contraseña', + 'new_password_again' => 'Repetir Contraseña Nueva', + 'new_email' => 'Nueva Dirección De Correo Electrónico', + 'first_name' => 'Primer Nombre', + 'last_name' => 'Apellido', + 'update_identitity' => 'Actualización De La Identidad', + 'username_help' => 'Su nombre de usuario debe ser único a su cuenta, y sólo pueden contener los siguientes caracteres: :requirements.', + ], + 'security' => [ + 'session_mgmt_disabled' => 'Su anfitrión no ha habilitado la capacidad de gestionar la cuenta de las sesiones a través de esta interfaz.', + 'header' => 'Seguridad De La Cuenta', + 'header_sub' => 'Control de sesiones activas y 2-Factor de Autenticación.', + 'sessions' => 'Sesiones Activas', + '2fa_header' => '2-Factor De Autenticación', + '2fa_token_help' => 'Introduzca el 2FA Token generado por la aplicación (Google Authenticatior, Authy, etc.).', + 'disable_2fa' => 'Deshabilitar 2-Factor De Autenticación', + '2fa_enabled' => '2-Factor de Autenticación está habilitada en esta cuenta y será necesario iniciar la sesión en el panel de. Si usted desea deshabilitar el 2FA, simplemente ingrese un token válido a continuación y envíe el formulario.', + '2fa_disabled' => '2-Factor de Autenticación está deshabilitado en tu cuenta! Usted debe habilitar 2FA con el fin de añadir un nivel extra de protección en su cuenta.', + 'enable_2fa' => 'Habilitar 2-Factor De Autenticación', + '2fa_qr' => 'Confgure 2FA en Su Dispositivo', + '2fa_checkpoint_help' => 'Utilice el 2FA aplicación en su teléfono para tomar una foto del código QR de la izquierda, o introducir manualmente el código debajo de ella. Una vez hecho esto, generar un token y entrar en él a continuación.', + '2fa_disable_error' => 'El 2FA token proporcionado no es válido. La protección no ha sido deshabilitado para esta cuenta.', + ], +]; diff --git a/resources/lang/es/command/messages.php b/resources/lang/es/command/messages.php new file mode 100644 index 000000000..6e8c3e95b --- /dev/null +++ b/resources/lang/es/command/messages.php @@ -0,0 +1,91 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'location' => [ + 'no_location_found' => 'No se pudo localizar un registro coincidente el código corto.', + 'ask_short' => 'Ubicación De Código Corto', + 'ask_long' => 'Descripción De La Localización', + 'created' => 'Creado con éxito una nueva ubicación (:name) con un ID :id.', + 'deleted' => 'Elimina correctamente la ubicación solicitada.', + ], + 'user' => [ + 'search_users' => 'Introduzca un nombre de Usuario, UUID, o Dirección de Correo electrónico', + 'select_search_user' => 'Id del usuario para borrar (Usa 0, para buscar).', + 'deleted' => 'Usuario borrado con éxito desde el Panel de.', + 'confirm_delete' => 'Está seguro de que desea borrar este usuario desde el Panel?', + 'no_users_found' => 'Los usuarios No se encontraron resultados para el término de búsqueda proporcionado.', + 'multiple_found' => 'Varias cuentas se han encontrado para la proporcionada por el usuario, no se puede eliminar un usuario, porque de el --no-interacción de la bandera.', + 'ask_admin' => 'Es este usuario administrador?', + 'ask_email' => 'Dirección De Correo Electrónico', + 'ask_username' => 'Nombre de usuario', + 'ask_name_first' => 'Primer Nombre', + 'ask_name_last' => 'Apellido', + 'ask_password' => 'Contraseña', + 'ask_password_tip' => 'Si desea crear una cuenta con una contraseña aleatoria enviado por correo electrónico al usuario, vuelva a ejecutar este comando (CTRL+C) y pasar el `--no-password` de la bandera.', + 'ask_password_help' => 'Las contraseñas deben tener al menos 8 caracteres y contener al menos una letra mayúscula y el número.', + '2fa_help_text' => [ + 'Este comando desactivará la autenticación de 2 factores para la cuenta de un usuario si está habilitada. Esto sólo debe ser utilizado como una cuenta de recuperación de comando si el usuario está bloqueado de su cuenta.', + 'Si esto no es lo que quería hacer, presione CTRL+C para salir de este proceso.', + ], + '2fa_disabled' => '2-Factor de autenticación ha sido desactivado por :email.', + ], + 'schedule' => [ + 'output_line' => 'Despacho de trabajo para la primera tarea en `programar` (:hash).', + ], + 'maintenance' => [ + 'deleting_service_backup' => 'Eliminar el servicio de copia de seguridad de archivo :file.', + ], + 'server' => [ + 'rebuild_failed' => 'Reconstruir la solicitud de ":name" (#:id) en el nodo ":node" con el error: :message', + ], + 'environment' => [ + 'mail' => [ + 'ask_smtp_host' => 'Host SMTP (e.g. smtp.google.com)', + 'ask_smtp_port' => 'Puerto SMTP', + 'ask_smtp_username' => 'El nombre de Usuario SMTP', + 'ask_smtp_password' => 'Contraseña SMTP', + 'ask_mailgun_domain' => 'Mailgun De Dominio', + 'ask_mailgun_secret' => 'Mailgun Secreto', + 'ask_mandrill_secret' => 'Mandrill Secreto', + 'ask_postmark_username' => 'Matasellos Clave de API', + 'ask_driver' => 'El controlador que debe ser utilizado para el envío de correos electrónicos?', + 'ask_mail_from' => 'Dirección de correo electrónico los correos electrónicos se originan a partir de', + 'ask_mail_name' => 'Nombre que los correos electrónicos deben aparecer a partir de', + 'ask_encryption' => 'Método de encriptación a utilizar', + ], + 'database' => [ + 'host_warning' => 'Es muy recomendable no usar "localhost" como el host de base de datos, como hemos visto, los frecuentes problemas de conexión de socket. Si desea utilizar una conexión local debe ser el uso de "127.Cero.Cero.1".', + 'host' => 'Host De Base De Datos', + 'port' => 'Puerto De Base De Datos', + 'database' => 'Nombre De Base De Datos', + 'username_warning' => 'El uso de la "raíz" de la cuenta para las conexiones de MySQL no sólo es muy mal visto, no está permitido por esta aplicación. Necesitarás haber creado un usuario de MySQL para este software.', + 'username' => 'Base De Datos De Nombre De Usuario', + 'password_defined' => 'Parece que ya tiene un usuario y contraseña de conexión definido, te gustaría cambiar?', + 'password' => 'Contraseña De Base De Datos', + 'connection_error' => 'No se puede conectar con el servidor MySQL usando los credenciales. El error devuelto fue ":error".', + 'creds_not_saved' => 'Sus credenciales de conexión NO se han guardado. Usted tendrá que proporcionar conexión válida la información antes de proceder.', + 'try_again' => 'Volver y probar otra vez?', + ], + 'app' => [ + 'app_url_help' => 'La dirección URL de la aplicación DEBE comenzar con https:// o http:// dependiendo de si usted está usando SSL o no. Si no se incluye el esquema de sus correos electrónicos y otros contenidos proporcionará un enlace a la ubicación incorrecta.', + 'app_url' => 'Dirección URL de la aplicación', + 'timezone_help' => 'La zona horaria debe coincidir con una de las zonas horarias compatibles de PHP. Si usted no está seguro, por favor consulte http://php.net/manual/es/zonas horarias.php.', + 'timezone' => 'La Zona Horaria De Aplicación', + 'cache_driver' => 'Caché De Controlador', + 'session_driver' => 'Controlador De Sesión', + 'using_redis' => 'Ha seleccionado el controlador Redis para una o más opciones, proporcione la información de conexión válida a continuación. En la mayoría de los casos se pueden utilizar los valores predeterminados a menos que usted haya modificado su configuración.', + 'redis_host' => 'Redis Host', + 'redis_password' => 'Redis Contraseña', + 'redis_port' => 'Redis Puerto', + 'redis_pass_defined' => 'Parece una contraseña ya está definida para Redis, te gustaría cambiar?', + 'redis_pass_help' => 'De forma predeterminada, un servidor Redis instancia no tiene contraseña ya que se ejecuta localmente y inaccessable para el mundo exterior. Si este es el caso, simplemente pulsa enter sin introducir un valor.', + ], + ], +]; diff --git a/resources/lang/es/exceptions.php b/resources/lang/es/exceptions.php new file mode 100644 index 000000000..907f96749 --- /dev/null +++ b/resources/lang/es/exceptions.php @@ -0,0 +1,56 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'daemon_connection_failed' => 'Hubo una excepción al intentar comunicarse con el demonio que resulta en un HTTP/:code código de respuesta. Esta excepción ha sido registrado.', + 'node' => [ + 'servers_attached' => 'Un nodo no debe tener servidores vinculados a la misma, en orden a ser eliminados.', + 'daemon_off_config_updated' => 'La configuración del demonio se ha actualizado, sin embargo hubo un error al intentar actualizar automáticamente el archivo de configuración del Demonio. Usted tendrá que actualizar manualmente el archivo de configuración (core.json) para el demonio para aplicar estos cambios. El demonio respondió con un HTTP/:code código de respuesta y el error ha sido iniciado.', + ], + 'allocations' => [ + 'too_many_ports' => 'La adición de más de 1000 puertos en un único momento no es compatible. Por favor, use un rango menor.', + 'invalid_mapping' => 'La cartografía proporcionada por :port no era válido y no puede ser procesado.', + 'cidr_out_of_range' => 'La notación CIDR sólo permite máscaras entre los /25 e /32.', + ], + 'service' => [ + 'delete_has_servers' => 'Un servicio con los servidores activos conectados a no se puede eliminar desde el Panel de.', + 'options' => [ + 'delete_has_servers' => 'Una opción de servicio con los servidores activos conectados a no se puede eliminar desde el Panel de.', + 'invalid_copy_id' => 'La opción de servicio seleccionado para la copia de una secuencia de comandos o bien no existe, o es copia de un mismo script.', + 'must_be_child' => 'La "Configuración de la Copia De la" directiva para que esta opción debe ser un niño opción para el servicio seleccionado.', + ], + 'variables' => [ + 'env_not_unique' => 'La variable de entorno :name debe ser único para esta opción de servicio.', + 'reserved_name' => 'La variable de entorno :name está protegido y no puede ser asignado a una variable.', + ], + ], + 'packs' => [ + 'delete_has_servers' => 'No se puede eliminar un paquete que está conectado a los servidores activos.', + 'update_has_servers' => 'No puede modificar la opción asociada ID cuando los servidores están conectados actualmente a un pack.', + 'invalid_upload' => 'El archivo no parece ser válido.', + 'invalid_mime' => 'El archivo no cumple con los requisitos tipo :type', + 'unreadable' => 'El archivo siempre y no puede ser abierto por el servidor.', + 'zip_extraction' => 'Una excepción se encontró al intentar extraer el archivo proporcionado en el servidor.', + 'invalid_archive_exception' => 'El pack archivo siempre parece que falta un archivo necesaria.alquitrán.gz o de importación.archivo json en el directorio de base de.', + ], + 'subusers' => [ + 'editing_self' => 'La edición de su propio subuser cuenta no está permitido.', + 'user_is_owner' => 'Usted puede agregar el propietario del servidor como un subuser para este servidor.', + 'subuser_exists' => 'Un usuario con esa dirección de correo electrónico ya está asignado como subuser para este servidor.', + ], + 'databases' => [ + 'delete_has_databases' => 'No se puede eliminar una base de datos de servidor de host que tiene bases de datos activas vinculados a ella.', + ], + 'tasks' => [ + 'chain_interval_too_long' => 'El intervalo máximo de tiempo para un encadenado tarea es de 15 minutos.', + ], + 'locations' => [ + 'has_nodes' => 'No se puede eliminar una ubicación que tenga activa de los nodos conectados a él.', + ], +]; diff --git a/resources/lang/es/navigation.php b/resources/lang/es/navigation.php new file mode 100644 index 000000000..7f70b0d36 --- /dev/null +++ b/resources/lang/es/navigation.php @@ -0,0 +1,29 @@ + 'Home', + 'account' => [ + 'header' => 'GESTIÓN DE CUENTAS', + 'my_account' => 'Mi Cuenta', + 'security_controls' => 'Controles de Seguridad', + 'api_access' => 'Acceso de API', + 'my_servers' => 'Mis Servidores', + ], + 'server' => [ + 'header' => 'GESTIÓN DEL SERVIDOR', + 'console' => 'Consola', + 'console-pop' => 'Consola de Pantalla Completa', + 'file_management' => 'Gestión de Archivos', + 'file_browser' => 'Explorador de Archivos', + 'create_file' => 'Crea archivo', + 'upload_files' => 'Sube archivos', + 'subusers' => 'Subusadores', + 'schedules' => 'Horarios', + 'configuration' => 'Configuración', + 'port_allocations' => 'Asignaciones de Puertos', + 'sftp_settings' => 'Configuración de SFTP', + 'startup_parameters' => 'Parámetros de Inicio', + 'databases' => 'Bases de Datos', + 'edit_file' => 'Edita Archivo', + ], +]; diff --git a/resources/lang/es/pagination.php b/resources/lang/es/pagination.php new file mode 100644 index 000000000..51862f2eb --- /dev/null +++ b/resources/lang/es/pagination.php @@ -0,0 +1,17 @@ + '« Anterior', + 'next' => 'Siguiente »', +]; diff --git a/resources/lang/es/passwords.php b/resources/lang/es/passwords.php new file mode 100644 index 000000000..b041d1fb3 --- /dev/null +++ b/resources/lang/es/passwords.php @@ -0,0 +1,19 @@ + 'Las contraseñas deben contener al menos 6 caracters y coincidir.', + 'reset' => 'Su contraseña ha sido cambiada.', + 'sent' => 'Le hemos enviado un correo de cambio de contraseña!', + 'token' => 'El código de cambio de contraseña es inválido.', + 'user' => 'No podemos encontrar un usuario con ese nombre.', +]; diff --git a/resources/lang/es/server.php b/resources/lang/es/server.php new file mode 100644 index 000000000..41aca3558 --- /dev/null +++ b/resources/lang/es/server.php @@ -0,0 +1,309 @@ + [ + 'title' => 'Visualización del Servidor :name', + 'header' => 'La Consola Del Servidor', + 'header_sub' => 'Control de su servidor en tiempo real.', + ], + 'schedule' => [ + 'header' => 'Schedule Manager', + 'header_sub' => 'Administre todos los horarios de este servidor en un solo lugar.', + 'current' => 'Horarios Actualizados', + 'new' => [ + 'header' => 'Crear Nuevo Horario', + 'header_sub' => 'Crear un nuevo conjunto de tareas programadas para este servidor.', + 'submit' => 'Crear Calendario', + ], + 'manage' => [ + 'header' => 'Administrar Horario', + 'submit' => 'La Programación De Actualización', + 'delete' => 'Eliminar Horario', + ], + 'task' => [ + 'time' => 'Tras', + 'action' => 'Realizar La Acción', + 'payload' => 'Con Una Carga Útil', + 'add_more' => 'Añadir Otra Tarea', + ], + 'actions' => [ + 'command' => 'Enviar Comando', + 'power' => 'El Poder De Acción', + ], + 'unnamed' => 'Sin Nombre Horario', + 'setup' => 'Configuración De La Programación', + 'day_of_week' => 'El día de la Semana', + 'day_of_month' => 'Día de Mes', + 'hour' => 'La hora del Día', + 'minute' => 'Hora de la Hora', + 'time_help' => 'La programación del sistema es compatible con el uso de Cronjob la sintaxis de la hora de definir cuando las tareas deben comenzar a correr. Utilice los campos de arriba para especificar cuando estas tareas deben empezar a ejecutar o seleccionar opciones de la selección múltiple de los menús.', + 'task_help' => 'Los tiempos para las tareas relativas a la definida anteriormente tarea. Cada programa puede tener más de 5 tareas que se le asignen tareas y no puede ser programado más de 15 minutos de distancia.', + ], + 'tasks' => [ + 'task_created' => 'Creado con éxito una nueva tarea en el Panel de.', + 'task_updated' => 'La tarea ha sido actualizado. Cualquier se encuentra en la cola de tareas acciones serán cancelados y ejecutar de nuevo en el próximo tiempo definido.', + 'header' => 'Tareas Programadas', + 'header_sub' => 'Automatizar el servidor.', + 'current' => 'Actual De Las Tareas Programadas', + 'actions' => [ + 'command' => 'Enviar Comando', + 'power' => 'Enviar La Opción De La Energía', + ], + 'new_task' => 'Agregar Nueva Tarea', + 'toggle' => 'Cambiar Estado', + 'new' => [ + 'header' => 'Nueva Tarea', + 'header_sub' => 'Crear una nueva tarea programada para este servidor.', + 'task_name' => 'Nombre De La Tarea', + 'day_of_week' => 'El día de la Semana', + 'custom' => 'Valor Personalizado', + 'day_of_month' => 'Día de Mes', + 'hour' => 'Hora', + 'minute' => 'Minutos', + 'sun' => 'Domingo', + 'mon' => 'Lunes', + 'tues' => 'Martes', + 'wed' => 'Miércoles', + 'thurs' => 'Jueves', + 'fri' => 'Viernes', + 'sat' => 'Sábado', + 'submit' => 'Crear Tarea', + 'type' => 'Tipo De Tarea', + 'chain_then' => 'Luego, Después De', + 'chain_do' => '¿', + 'chain_arguments' => 'Con Argumentos', + 'payload' => 'La Tarea De Carga', + 'payload_help' => 'Por ejemplo, si selecciona Enviar Comando introduzca el comando. Si selecciona Enviar la Opción de la Energía poner el poder de la acción aquí (e.g. restart).', + ], + 'edit' => [ + 'header' => 'Gestionar Tareas', + 'submit' => 'La Tarea De Actualización', + ], + ], + 'users' => [ + 'header' => 'Administrar Usuarios', + 'header_sub' => 'Controlar quién puede acceder a su servidor de.', + 'configure' => 'Configurar Los Permisos De', + 'list' => 'Cuentas con Acceso', + 'add' => 'Agregar Nuevo Subuser', + 'update' => 'Actualización Subuser', + 'user_assigned' => 'Correctamente asignado un nuevo subuser a este servidor.', + 'user_updated' => 'Actualizado correctamente los permisos de.', + 'edit' => [ + 'header' => 'Editar Subuser', + 'header_sub' => 'Modificar el acceso del usuario al servidor.', + ], + 'new' => [ + 'header' => 'Añadir Nuevo Usuario', + 'header_sub' => 'Agregar un nuevo usuario con permisos para este servidor.', + 'email' => 'Dirección De Correo Electrónico', + 'email_help' => 'Introduzca la dirección de correo electrónico para el usuario que quiere invitar a administrar este servidor.', + 'power_header' => 'Administración De Energía', + 'file_header' => 'La Gestión De Archivos', + 'subuser_header' => 'Subuser De Gestión', + 'server_header' => 'Administración Del Servidor', + 'task_header' => 'La Programación De La Administración', + 'database_header' => 'Administración De Base De Datos', + 'power_start' => [ + 'title' => 'Inicio Del Servidor', + 'description' => 'Permite al usuario iniciar el servidor.', + ], + 'power_stop' => [ + 'title' => 'Detener El Servidor', + 'description' => 'Permite al usuario detener el servidor.', + ], + 'power_restart' => [ + 'title' => 'Reinicie El Servidor', + 'description' => 'Permite al usuario reiniciar el servidor.', + ], + 'power_kill' => [ + 'title' => 'Matar Servidor', + 'description' => 'Permite que el usuario pueda matar el proceso del servidor.', + ], + 'send_command' => [ + 'title' => 'Enviar Comandos De La Consola', + 'description' => 'Permite el envío de un comando desde la consola. Si el usuario no tiene permiso para detener o reiniciar, no puede enviar el comando de detención de la aplicación.', + ], + 'view_sftp' => [ + 'title' => 'SFTP permitido', + 'description' => 'Permite al usuario conectarse al servidor SFTP proporcionado por el daemon.', + ], + 'list_files' => [ + 'title' => 'Lista De Archivos', + 'description' => 'Permite al usuario a la lista de todos los archivos y carpetas en el servidor, pero no ver el contenido del archivo.', + ], + 'edit_files' => [ + 'title' => 'Editar Archivos', + 'description' => 'Permite al usuario abrir un archivo solo para visualización. SFTP no se ve afectado por este permiso.', + ], + 'save_files' => [ + 'title' => 'Guardar Archivos', + 'description' => 'Permite que el usuario guarde el archivo modificado contenido. SFTP no se ve afectado por este permiso.', + ], + 'move_files' => [ + 'title' => 'Renombrar Y Mover Archivos', + 'description' => 'Permite al usuario mover y renombrar archivos y carpetas en el sistema de ficheros.', + ], + 'copy_files' => [ + 'title' => 'Copiar Archivos', + 'description' => 'Permite a los usuarios copiar archivos y carpetas en el sistema de ficheros.', + ], + 'compress_files' => [ + 'title' => 'Comprimir Los Archivos', + 'description' => 'Permite que el usuario pueda hacer de los archivos de los archivos y carpetas en el sistema.', + ], + 'decompress_files' => [ + 'title' => 'Descomprimir Los Archivos', + 'description' => 'Permite que el usuario para descomprimir .zip y .alquitrán(.gz) archivos.', + ], + 'create_files' => [ + 'title' => 'Crear Archivos', + 'description' => 'Permite al usuario crear un nuevo archivo en el panel de.', + ], + 'upload_files' => [ + 'title' => 'Subir Archivos', + 'description' => 'Permite a los usuarios cargar archivos a través del administrador de archivos.', + ], + 'delete_files' => [ + 'title' => 'Eliminar Archivos', + 'description' => 'Permite al usuario eliminar archivos del sistema.', + ], + 'download_files' => [ + 'title' => 'Descargar Archivos', + 'description' => 'Permite al usuario descargar archivos. Si un usuario se da este permiso se puede descargar y ver el contenido del archivo, incluso si ese permiso no está asignado en el panel.', + ], + 'list_subusers' => [ + 'title' => 'Lista De Subusers', + 'description' => 'Permite al usuario ver una lista de todos los subusers asignadas al servidor.', + ], + 'view_subuser' => [ + 'title' => 'Ver Subuser', + 'description' => 'Permite al usuario ver los permisos asignados a subusers.', + ], + 'edit_subuser' => [ + 'title' => 'Editar Subuser', + 'description' => 'Permite a un usuario para editar los permisos asignados a otras subusers.', + ], + 'create_subuser' => [ + 'title' => 'Crear Subuser', + 'description' => 'Permite al usuario crear más subusers en el servidor.', + ], + 'delete_subuser' => [ + 'title' => 'Eliminar Subuser', + 'description' => 'Permite a un usuario para eliminar otros subusers en el servidor.', + ], + 'set_connection' => [ + 'title' => 'Conjunto De Conexión Predeterminado', + 'description' => 'Permite al usuario establecer la conexión por defecto que se utiliza para un servidor, así como ver los puertos disponibles.', + ], + 'view_startup' => [ + 'title' => 'Vista De Comandos De Inicio', + 'description' => 'Permite al usuario ver los comandos de inicio y las variables asociadas a un servidor.', + ], + 'edit_startup' => [ + 'title' => 'Edición De Comandos De Inicio', + 'description' => 'Permite que un usuario modifique el inicio variables para un servidor.', + ], + 'list_schedules' => [ + 'title' => 'Lista De Horarios', + 'description' => 'Permite a un usuario a la lista de todos los horarios (activado y desactivado) para este servidor.', + ], + 'view_schedule' => [ + 'title' => 'Ver Programación', + 'description' => 'Permite a un usuario ver los detalles de un programa específico, incluidas todas las tareas asignadas', + ], + 'toggle_schedule' => [ + 'title' => 'Alternar Horario', + 'description' => 'Permite a un usuario para cambiar de un programa a ser activo o inactivo.', + ], + 'queue_schedule' => [ + 'title' => 'Cola De Horario', + 'description' => 'Permite a un usuario poner en cola un horario para ejecutar sus tareas en el siguiente ciclo de proceso.', + ], + 'edit_schedule' => [ + 'title' => 'Modificar La Programación', + 'description' => 'Permite a un usuario editar un horario que incluye todas las tareas del cronograma. Esto permitirá al usuario eliminar tareas individuales, pero no eliminar el calendario en sí.', + ], + 'create_schedule' => [ + 'title' => 'Crear Calendario', + 'description' => 'Permite a un usuario crear una nueva programación.', + ], + 'delete_schedule' => [ + 'title' => 'Eliminar Horario', + 'description' => 'Permite a un usuario para eliminar un programa desde el servidor.', + ], + 'view_databases' => [ + 'title' => 'Ver Detalles De Base De Datos', + 'description' => 'Permite al usuario ver todas las bases de datos asociadas con este servidor, incluidos los nombres de usuario y contraseñas de las bases de datos de.', + ], + 'reset_db_password' => [ + 'title' => 'Restablecer Contraseña De Base De Datos', + 'description' => 'Permite a un usuario para restablecer las contraseñas de las bases de datos de.', + ], + ], + ], + 'files' => [ + 'exceptions' => [ + 'invalid_mime' => 'Este tipo de archivo no se puede editar a través del editor incorporado del Panel.', + 'max_size' => 'Este archivo es demasiado grande para editarlo a través del editor incorporado del Panel.', + ], + 'header' => 'El Administrador De Archivos', + 'header_sub' => 'Administrar todos tus archivos directamente desde la web.', + 'loading' => 'La carga inicial de la estructura del archivo, esto puede tardar unos segundos.', + 'path' => 'Cuando la configuración de rutas de archivo en su servidor de plugins o configuración que debe utilizar :path de acceso como base de la ruta. El tamaño máximo para la web basado en la carga de archivos a este nodo es :size de la.', + 'seconds_ago' => 'hace segundos', + 'file_name' => 'Nombre De Archivo', + 'size' => 'Tamaño', + 'last_modified' => 'Última Modificación', + 'add_new' => 'Agregar Nuevo Archivo', + 'add_folder' => 'Agregar Nueva Carpeta', + 'mass_actions' => 'Acciones masivas', + 'delete' => 'Eliminar', + 'edit' => [ + 'header' => 'Editar El Archivo', + 'header_sub' => 'Hacer modificaciones a un archivo de la web.', + 'save' => 'Guardar Archivo', + 'return' => 'Volver al Administrador de Archivos', + ], + 'add' => [ + 'header' => 'Nuevo Archivo', + 'header_sub' => 'Crear un nuevo archivo en su servidor.', + 'name' => 'Nombre De Archivo', + 'create' => 'Crear Archivo', + ], + ], + 'config' => [ + 'startup' => [ + 'header' => 'Iniciar La Configuración', + 'header_sub' => 'El servidor de Control de inicio de argumentos.', + 'command' => 'De Comandos De Inicio', + 'edit_params' => 'Los Parámetros De Edición', + 'update' => 'Actualización De Parámetros De Inicio', + 'startup_var' => 'De Comandos De Inicio De La Variable', + 'startup_regex' => 'De Entrada Las Reglas De', + ], + 'sftp' => [ + 'header' => 'SFTP Configuración', + 'header_sub' => 'Detalles de la cuenta para SFTP.', + 'details' => 'SFTP Detalles', + 'conn_addr' => 'Dirección De Conexión', + 'warning' => 'Asegúrese de que su cliente está configurado para utilizar SFTP y FTP no o FTPS para las conexiones, hay una diferencia entre los protocolos.', + ], + 'database' => [ + 'header' => 'Las bases de datos de', + 'header_sub' => 'Todas las bases de datos disponibles para este servidor.', + 'your_dbs' => 'Las Bases De Datos', + 'host' => 'Host MySQL', + 'reset_password' => 'Para Restablecer La Contraseña', + 'no_dbs' => 'No existen bases de datos mencionadas en este servidor.', + 'add_db' => 'Agregar una nueva base de datos.', + ], + 'allocation' => [ + 'header' => 'Servidor De Asignaciones', + 'header_sub' => 'Control de la IPs y los puertos disponibles en este servidor.', + 'available' => 'Disponible Asignaciones', + 'help' => 'La Asignación De La Ayuda', + 'help_text' => 'La lista a la izquierda incluye todos los IPs y los puertos que están abiertos para su servidor que se utilizará para las conexiones entrantes.', + ], + ], +]; diff --git a/resources/lang/es/strings.php b/resources/lang/es/strings.php new file mode 100644 index 000000000..570663f3e --- /dev/null +++ b/resources/lang/es/strings.php @@ -0,0 +1,86 @@ + 'Email', + 'user_identifier' => 'Nombre de usuario o Email', + 'password' => 'Contraseña', + 'confirm_password' => 'Confirma contraseña', + 'login' => 'Iniciar Sesión', + 'home' => 'Casa', + 'servers' => 'Servidores', + 'id' => 'ID', + 'name' => 'Nombre', + 'node' => 'Nodo', + 'connection' => 'Conexión', + 'memory' => 'Memoria', + 'cpu' => 'CPU', + 'status' => 'Estado', + 'search' => 'Busca', + 'suspended' => 'Suspendido', + 'account' => 'Cuenta', + 'security' => 'Seguridad', + 'ip' => 'Dirección IP', + 'last_activity' => 'Última Actividad', + 'revoke' => 'Revoca', + '2fa_token' => 'Token de Autenticación', + 'submit' => 'Envia', + 'close' => 'Cierra', + 'settings' => 'Ajustes', + 'configuration' => 'Configuración', + 'sftp' => 'SFTP', + 'databases' => 'Bases de Datos', + 'memo' => 'Memorándum', + 'created' => 'Creado', + 'expires' => 'Expira', + 'public_key' => 'Llave pública', + 'api_access' => 'Acceso Api', + 'never' => 'Nunca', + 'sign_out' => 'Desconectar', + 'admin_control' => 'Control de administración', + 'required' => 'Necesario', + 'port' => 'Puerto', + 'username' => 'Nombre de usuario', + 'database' => 'Bases de Dato', + 'new' => 'Nuevo', + 'danger' => 'Peligro', + 'create' => 'Crea', + 'select_all' => 'Selecciona Todo', + 'select_none' => 'Selecciona Ninguno', + 'alias' => 'Alias', + 'primary' => 'Primario', + 'make_primary' => 'Hace primaria', + 'none' => 'Ninguno', + 'cancel' => 'Cancela', + 'created_at' => 'Creado En', + 'action' => 'Acción', + 'data' => 'Datos', + 'queued' => 'En Cola', + 'last_run' => 'Última Carrera', + 'next_run' => 'Próxima Carrera', + 'not_run_yet' => 'Aún no ha corrido', + 'yes' => 'Sí', + 'no' => 'No', + 'delete' => 'Borra', + '2fa' => '2FA', + 'logout' => 'Cerrar Sesión', + 'admin_cp' => 'Panel de Control de Administración', + 'optional' => 'Opcional', + 'read_only' => 'Solo Lectura', + 'relation' => 'Relación', + 'owner' => 'Propietario', + 'admin' => 'Administrador', + 'subuser' => 'Subusador', + 'captcha_invalid' => 'El captcha proporcionado no es válido.', + 'tasks' => 'Tareas', + 'seconds' => 'Segundos', + 'minutes' => 'Minutos', + 'days' => [ + 'sun' => 'Domingo', + 'mon' => 'Lunes', + 'tues' => 'Martes', + 'wed' => 'Miércoles', + 'thurs' => 'Jueves', + 'fri' => 'Viernes', + 'sat' => 'Sábado', + ], +]; diff --git a/resources/lang/es/validation.php b/resources/lang/es/validation.php new file mode 100644 index 000000000..466639f05 --- /dev/null +++ b/resources/lang/es/validation.php @@ -0,0 +1,120 @@ + 'El campo :attribute debe ser aceptado.', + 'active_url' => 'El campo :attribute no es una URL válida.', + 'after' => 'El campo :attribute debe ser una fecha después de :date.', + 'after_or_equal' => 'El campo :attribute debe ser una fecha igual o después de :date.', + 'alpha' => 'El campo :attribute sólo puede contener letras.', + 'alpha_dash' => 'El campo :attribute sólo puede contener letras, números y guiones.', + 'alpha_num' => 'El campo :attribute sólo puede contener letras y números.', + 'array' => 'El campo :attribute debe ser un arreglo.', + 'before' => 'El campo :attribute debe ser una fecha antes :date.', + 'before_or_equal' => 'El campo :attribute debe ser una fecha antes o igual a :date.', + 'between' => [ + 'numeric' => 'El campo :attribute debe estar entre :min - :max.', + 'file' => 'El campo :attribute debe estar entre :min - :max kilobytes.', + 'string' => 'El campo :attribute debe estar entre :min - :max caracteres.', + 'array' => 'El campo :attribute debe tener entre :min y :max elementos.', + ], + 'boolean' => 'El campo :attribute debe ser verdadero o falso.', + 'confirmed' => 'El campo de confirmación de :attribute no coincide.', + 'date' => 'El campo :attribute no es una fecha válida.', + 'date_format' => 'El campo :attribute no corresponde con el formato :format.', + 'different' => 'Los campos :attribute y :other deben ser diferentes.', + 'digits' => 'El campo :attribute debe ser de :digits dígitos.', + 'digits_between' => 'El campo :attribute debe tener entre :min y :max dígitos.', + 'dimensions' => 'El campo :attribute no tiene una dimensión válida.', + 'distinct' => 'El campo :attribute tiene un valor duplicado.', + 'email' => 'El formato del :attribute es inválido.', + 'exists' => 'El campo :attribute seleccionado es inválido.', + 'file' => 'El campo :attribute debe ser un archivo.', + 'filled' => 'El campo :attribute es requerido.', + 'image' => 'El campo :attribute debe ser una imagen.', + 'in' => 'El campo :attribute seleccionado es inválido.', + 'in_array' => 'El campo :attribute no existe en :other.', + 'integer' => 'El campo :attribute debe ser un entero.', + 'ip' => 'El campo :attribute debe ser una dirección IP válida.', + 'json' => 'El campo :attribute debe ser una cadena JSON válida.', + 'max' => [ + 'numeric' => 'El campo :attribute debe ser menor que :max.', + 'file' => 'El campo :attribute debe ser menor que :max kilobytes.', + 'string' => 'El campo :attribute debe ser menor que :max caracteres.', + 'array' => 'El campo :attribute puede tener hasta :max elementos.', + ], + 'mimes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'mimetypes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'min' => [ + 'numeric' => 'El campo :attribute debe tener al menos :min.', + 'file' => 'El campo :attribute debe tener al menos :min kilobytes.', + 'string' => 'El campo :attribute debe tener al menos :min caracteres.', + 'array' => 'El campo :attribute debe tener al menos :min elementos.', + ], + 'not_in' => 'El campo :attribute seleccionado es invalido.', + 'numeric' => 'El campo :attribute debe ser un número.', + 'present' => 'El campo :attribute debe estar presente.', + 'regex' => 'El formato del campo :attribute es inválido.', + 'required' => 'El campo :attribute es requerido.', + 'required_if' => 'El campo :attribute es requerido cuando el campo :other es :value.', + 'required_unless' => 'El campo :attribute es requerido a menos que :other esté presente en :values.', + 'required_with' => 'El campo :attribute es requerido cuando :values está presente.', + 'required_with_all' => 'El campo :attribute es requerido cuando :values está presente.', + 'required_without' => 'El campo :attribute es requerido cuando :values no está presente.', + 'required_without_all' => 'El campo :attribute es requerido cuando ningún :values está presente.', + 'same' => 'El campo :attribute y :other debe coincidir.', + 'size' => [ + 'numeric' => 'El campo :attribute debe ser :size.', + 'file' => 'El campo :attribute debe tener :size kilobytes.', + 'string' => 'El campo :attribute debe tener :size caracteres.', + 'array' => 'El campo :attribute debe contener :size elementos.', + ], + 'string' => 'El campo :attribute debe ser una cadena.', + 'timezone' => 'El campo :attribute debe ser una zona válida.', + 'unique' => 'El campo :attribute ya ha sido tomado.', + 'uploaded' => 'El campo :attribute no ha podido ser cargado.', + 'url' => 'El formato de :attribute es inválido.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [ + 'username' => 'usuario', + 'password' => 'contraseña', + ], +]; diff --git a/resources/themes/pterodactyl/admin/api/index.blade.php b/resources/themes/pterodactyl/admin/api/index.blade.php new file mode 100644 index 000000000..ece51699f --- /dev/null +++ b/resources/themes/pterodactyl/admin/api/index.blade.php @@ -0,0 +1,103 @@ +@extends('layouts.admin') + +@section('title') + Application API +@endsection + +@section('content-header') +

    Application APIControl access credentials for manging this Panel via the API.

    + +@endsection + +@section('content') +
    +
    +
    +
    +

    Credentials List

    + +
    +
    + + + + + + + + + @foreach($keys as $key) + + + + + + + + @endforeach +
    KeyMemoLast UsedCreated
    {{ $key->identifier }}{{ decrypt($key->token) }}{{ $key->memo }} + @if(!is_null($key->last_used_at)) + @datetimeHuman($key->last_used_at) + @else + — + @endif + @datetimeHuman($key->created_at) + + + +
    +
    +
    +
    +
    +@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/api/new.blade.php b/resources/themes/pterodactyl/admin/api/new.blade.php new file mode 100644 index 000000000..b5876ee8c --- /dev/null +++ b/resources/themes/pterodactyl/admin/api/new.blade.php @@ -0,0 +1,70 @@ +@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/themes/pterodactyl/admin/databases/index.blade.php b/resources/themes/pterodactyl/admin/databases/index.blade.php index e8e0c21ca..df1d13bb7 100644 --- a/resources/themes/pterodactyl/admin/databases/index.blade.php +++ b/resources/themes/pterodactyl/admin/databases/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -117,7 +103,7 @@
    - @foreach($services as $service) - - @endforeach - + +
    + +

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

    +
    - + -

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

    +

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

    -

    A description of this service that will be displayed throughout the panel as needed.

    +

    A description of this Egg.

    - - -

    This should be a unique identifer for this service option that is not used for any other service options. Must be alpha-numeric and no more than 60 characters in length.

    -
    -
    - + -

    The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.

    +

    The default docker image that should be used for new servers using this Egg. This can be changed per-server.

    - - -

    The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.

    + + +

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

    @@ -99,9 +83,9 @@
    -

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

    +

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

    @@ -129,7 +113,7 @@
    @@ -142,15 +126,15 @@ {!! Theme::js('vendor/lodash/lodash.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/admin/nests/new.blade.php b/resources/themes/pterodactyl/admin/nests/new.blade.php new file mode 100644 index 000000000..ed7fa3cdd --- /dev/null +++ b/resources/themes/pterodactyl/admin/nests/new.blade.php @@ -0,0 +1,52 @@ +{{-- 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') + 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 emcompasses all of the eggs within the nest.

    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +@endsection diff --git a/resources/themes/pterodactyl/admin/nests/view.blade.php b/resources/themes/pterodactyl/admin/nests/view.blade.php new file mode 100644 index 000000000..fe1e49462 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nests/view.blade.php @@ -0,0 +1,122 @@ +{{-- 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') + 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 emcompasses 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/themes/pterodactyl/admin/nodes/index.blade.php b/resources/themes/pterodactyl/admin/nodes/index.blade.php index 279cc31f8..a96d32240 100644 --- a/resources/themes/pterodactyl/admin/nodes/index.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -69,7 +55,7 @@ @foreach ($nodes as $node) - + {{ $node->name }} {{ $node->location->short }} {{ $node->memory }} MB @@ -109,8 +95,14 @@ title: 'v' + data.version, }); $(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heartbeat faa-pulse animated').css('color', '#50af51'); - }).fail(function () { + }).fail(function (error) { + var errorText = 'Error connecting to node! Check browser console for details.'; + try { + errorText = error.responseJSON.errors[0].detail || errorText; + } catch (ex) {} + $(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heart-o').css('color', '#d9534f'); + $(element).find('i').tooltip({ title: errorText }); }); }).promise().done(function () { setTimeout(pingNodes, 10000); diff --git a/resources/themes/pterodactyl/admin/nodes/new.blade.php b/resources/themes/pterodactyl/admin/nodes/new.blade.php index 98765bee0..f766800db 100644 --- a/resources/themes/pterodactyl/admin/nodes/new.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/new.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -43,14 +29,18 @@
    - +

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

    @@ -58,6 +48,7 @@
    +
    @@ -70,7 +61,7 @@
    - +

    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.

    @@ -81,11 +72,15 @@
    - + isSecure()) disabled @endif>
    -

    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.

    + @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
    @@ -119,14 +114,14 @@
    - + MB
    - + %
    @@ -138,14 +133,14 @@
    - + MB
    - + %
    diff --git a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php index 9c9ef5c78..75b40811c 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -86,9 +72,11 @@ @endforeach
    - + @if($node->allocations->hasPages()) + + @endif
    @@ -102,7 +90,7 @@
    @@ -144,7 +132,7 @@
    @@ -168,10 +156,12 @@ $('#pAllocationIP').select2({ tags: true, maximumSelectionLength: 1, + selectOnClose: true, tokenSeparators: [',', ' '], }); $('#pAllocationPorts').select2({ tags: true, + selectOnClose: true, tokenSeparators: [',', ' '], }); $('button[data-action="deallocate"]').click(function (event) { @@ -191,7 +181,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.nodes.view.allocation.removeSingle', { id: Pterodactyl.node.id, allocation: allocation }), + url: Router.route('admin.nodes.view.allocation.removeSingle', { node: Pterodactyl.node.id, allocation: allocation }), headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function (data) { element.parent().parent().addClass('warning').delay(100).fadeOut(); diff --git a/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php b/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php index 8193eaeb9..3fd6b3eb8 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- 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/themes/pterodactyl/admin/nodes/view/index.blade.php b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php index 1dc80007d..8dbaa8664 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/index.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -58,7 +44,7 @@ - + @@ -144,7 +130,7 @@ (function getInformation() { $.ajax({ method: 'GET', - url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}', + url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/v1', timeout: 5000, headers: { 'X-Access-Token': '{{ $node->daemonSecret }}' diff --git a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php index 0033dac2e..b648c760c 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -70,7 +56,7 @@ - + diff --git a/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php index b0624af28..d35a80c4e 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -218,6 +204,7 @@ diff --git a/resources/themes/pterodactyl/admin/packs/index.blade.php b/resources/themes/pterodactyl/admin/packs/index.blade.php index ca21678c0..270bea96b 100644 --- a/resources/themes/pterodactyl/admin/packs/index.blade.php +++ b/resources/themes/pterodactyl/admin/packs/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -57,7 +43,7 @@ - @foreach ($packs as $pack) @@ -66,7 +52,7 @@ - + @endforeach diff --git a/resources/themes/pterodactyl/admin/packs/modal.blade.php b/resources/themes/pterodactyl/admin/packs/modal.blade.php index fd4263575..2ce57f2b1 100644 --- a/resources/themes/pterodactyl/admin/packs/modal.blade.php +++ b/resources/themes/pterodactyl/admin/packs/modal.blade.php @@ -10,17 +10,17 @@
    - - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach -

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

    +

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

    diff --git a/resources/themes/pterodactyl/admin/packs/new.blade.php b/resources/themes/pterodactyl/admin/packs/new.blade.php index 540717ef4..35acdb540 100644 --- a/resources/themes/pterodactyl/admin/packs/new.blade.php +++ b/resources/themes/pterodactyl/admin/packs/new.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -66,12 +52,12 @@

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

    - - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach @@ -138,7 +124,7 @@ @section('footer-scripts') @parent diff --git a/resources/themes/pterodactyl/admin/packs/view.blade.php b/resources/themes/pterodactyl/admin/packs/view.blade.php index 03b331bee..0f4a38e7b 100644 --- a/resources/themes/pterodactyl/admin/packs/view.blade.php +++ b/resources/themes/pterodactyl/admin/packs/view.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -55,6 +41,11 @@

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

    +
    + + +

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

    +
    @@ -65,12 +56,12 @@
    - - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach @@ -107,42 +98,13 @@
    -
    -
    -
    -
    -

    Stored Files

    -
    -
    -
    Daemon Version (Latest: {{ Version::getDaemon() }}) (Latest: {{ $version->getDaemon() }})
    System Information{{ $server->uuidShort }} {{ $server->name }} {{ $server->user->username }}{{ $server->service->name }} ({{ $server->option->name }}){{ $server->nest->name }} ({{ $server->egg->name }}) NaN / {{ $server->memory === 0 ? '∞' : $server->memory }} MB {{ $server->disk }} MB NaN %Pack Name Version DescriptionOption + Egg Servers
    {{ $pack->name }} {{ $pack->version }} {{ str_limit($pack->description, 150) }}{{ $pack->option->name }}{{ $pack->egg->name }} {{ $pack->servers_count }}
    - - - - - - @foreach($pack->files() as $file) - - - - - - @endforeach -
    NameSHA1 HashFile Size
    {{ $file->name }}{{ $file->hash }}{{ $file->size }}
    -
    - -
    -
    -
    @@ -187,6 +149,6 @@ @section('footer-scripts') @parent @endsection diff --git a/resources/themes/pterodactyl/admin/servers/index.blade.php b/resources/themes/pterodactyl/admin/servers/index.blade.php index 59d1c363a..24fe4d81f 100644 --- a/resources/themes/pterodactyl/admin/servers/index.blade.php +++ b/resources/themes/pterodactyl/admin/servers/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -53,20 +39,19 @@ - + - + @foreach ($servers as $server) - + - + @endforeach @@ -94,3 +83,13 @@ @endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/new.blade.php b/resources/themes/pterodactyl/admin/servers/new.blade.php index eaf20445f..bfb6760b4 100644 --- a/resources/themes/pterodactyl/admin/servers/new.blade.php +++ b/resources/themes/pterodactyl/admin/servers/new.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -49,7 +35,7 @@
    - +
    @@ -78,21 +64,20 @@
    - - @foreach($locations as $location) - + + @foreach($location->nodes as $node) + + + + @endforeach + @endforeach -

    The location in which this server will be deployed.

    -
    -
    - -

    The node which this server will be deployed to.

    @@ -100,18 +85,12 @@

    The main allocation that will be assigned to this server.

    -
    +

    Additional allocations to assign to this server on creation.

    -
    @@ -136,20 +115,9 @@ MB -
    - -
    - - - - -
    -
    - - @@ -186,38 +152,38 @@
    -

    Service Configuration

    +

    Nest Configuration

    - - + @foreach($nests as $nest) + + >{{ $nest->name }} @endforeach -

    Select the type of service that this server will be running.

    +

    Select the Nest that this server will be grouped under.

    - - -

    Select the type of sub-service that this server will be running.

    + + +

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

    - + -

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

    +

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

    - +
    -

    If the selected Option has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

    +

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

    @@ -229,14 +195,9 @@
    - - -

    This is the default Docker container that will be used to run this server.

    -
    -
    - - -

    If you would like to use a custom Docker container please enter it here, otherwise leave empty.

    + + +

    This is the default Docker image that will be used to run this server.

    diff --git a/resources/themes/pterodactyl/admin/servers/view/build.blade.php b/resources/themes/pterodactyl/admin/servers/view/build.blade.php index 30e13407d..f6e9e607b 100644 --- a/resources/themes/pterodactyl/admin/servers/view/build.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/build.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- 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/themes/pterodactyl/admin/servers/view/database.blade.php b/resources/themes/pterodactyl/admin/servers/view/database.blade.php index 562b890e0..6c556137d 100644 --- a/resources/themes/pterodactyl/admin/servers/view/database.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/database.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -54,6 +40,9 @@
    +
    + Database passwords can be viewed when visiting this server on the front-end. +

    Active Databases

    @@ -91,8 +80,8 @@
    - - @foreach($hosts as $host) @endforeach @@ -107,8 +96,8 @@
    - - + +

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

    @@ -142,7 +131,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.servers.view.database.delete', { id: '{{ $server->id }}', database: self.data('id') }), + url: Router.route('admin.servers.view.database.delete', { server: '{{ $server->id }}', database: self.data('id') }), headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function () { self.parent().parent().slideUp(); @@ -163,7 +152,7 @@ $(this).addClass('disabled').find('i').addClass('fa-spin'); $.ajax({ type: 'PATCH', - url: Router.route('admin.servers.view.database', { id: '{{ $server->id }}' }), + url: Router.route('admin.servers.view.database', { server: '{{ $server->id }}' }), headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, data: { database: $(this).data('id') }, }).done(function (data) { diff --git a/resources/themes/pterodactyl/admin/servers/view/delete.blade.php b/resources/themes/pterodactyl/admin/servers/view/delete.blade.php index 6b47d70d6..4690e7173 100644 --- a/resources/themes/pterodactyl/admin/servers/view/delete.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/delete.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- 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/themes/pterodactyl/admin/servers/view/details.blade.php b/resources/themes/pterodactyl/admin/servers/view/details.blade.php index 8519a16c2..60bcded6d 100644 --- a/resources/themes/pterodactyl/admin/servers/view/details.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/details.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -53,7 +39,7 @@
    -
    +

    Base Information

    @@ -77,43 +63,15 @@

    A brief description of this server.

    -
    - - -

    This token should not be shared with anyone as it has full control over this server.

    -
    -
    - -

    Resetting this token will cause any requests using the old token to fail.

    -
    -
    -
    -
    -

    Container Setup

    -
    -
    -
    -
    - - -

    The docker image to use for this server. The default image for this service and option combination is {{ $server->option->docker_image }}.

    -
    -
    - - -
    -
    @endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/index.blade.php b/resources/themes/pterodactyl/admin/servers/view/index.blade.php index ee492c0ca..58deaa924 100644 --- a/resources/themes/pterodactyl/admin/servers/view/index.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -24,7 +10,7 @@ @endsection @section('content-header') -

    {{ $server->name }}{{ $server->uuid }}

    +

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

    ID Server NameUUID OwnerUsername Node Connection
    {{ $server->uuidShort }} {{ $server->name }}{{ $server->uuid }} {{ $server->user->username }}{{ $server->username }} {{ $server->node->name }} {{ $server->allocation->alias }}:{{ $server->allocation->port }} @@ -80,6 +65,10 @@ Active @endif + + +
    - - - - - - - - - - - - - - + + @@ -180,30 +154,3 @@ @endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/manage.blade.php b/resources/themes/pterodactyl/admin/servers/view/manage.blade.php index a736f5fd3..7b56a8034 100644 --- a/resources/themes/pterodactyl/admin/servers/view/manage.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/manage.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- 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/themes/pterodactyl/admin/servers/view/startup.blade.php b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php index c83b48cb0..ab2ac46f7 100644 --- a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -92,63 +78,52 @@

    - - + @foreach($nests as $nest) + + >{{ $nest->name }} @endforeach -

    Select the type of service that this server will be running.

    +

    Select the Nest that this server will be grouped into.

    - - -

    Select the type of sub-service that this server will be running.

    + + +

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

    - + -

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

    +

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

    skip_scripts) checked @endif /> - +
    -

    If the selected Option has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

    +

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

    +
    + + +
    +
    +

    Docker Container Configuration

    +
    +
    +
    + + +

    The Docker image to use for this server. The default image for the selected egg is .

    -
    - @foreach($server->option->variables as $variable) -
    -
    -
    -

    {{ $variable->name }}

    -
    -
    - -

    {{ $variable->description }}

    -

    - @if($variable->required)Required@elseOptional@endif - @if($variable->user_viewable)Visible@elseHidden@endif - @if($variable->user_editable)Editable@elseLocked@endif -

    -
    - -
    -
    - @endforeach -
    +
    @@ -159,44 +134,32 @@ {!! Theme::js('vendor/lodash/lodash.js') !!} diff --git a/resources/themes/pterodactyl/admin/services/functions.blade.php b/resources/themes/pterodactyl/admin/services/functions.blade.php deleted file mode 100644 index 3f0e89d43..000000000 --- a/resources/themes/pterodactyl/admin/services/functions.blade.php +++ /dev/null @@ -1,88 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Services → {{ $service->name }} → Functions -@endsection - -@section('content-header') -

    {{ $service->name }}Extend the default daemon functions using this service file.

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

    Functions Control

    -
    -
    -
    -
    {{ $service->index_file }}
    - -
    - - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/admin/services/index.blade.php b/resources/themes/pterodactyl/admin/services/index.blade.php deleted file mode 100644 index a943355e7..000000000 --- a/resources/themes/pterodactyl/admin/services/index.blade.php +++ /dev/null @@ -1,67 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Services -@endsection - -@section('content-header') -

    ServicesAll services currently available on this system.

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

    Configured Services

    - -
    -
    -
    UUID{{ $server->uuid }}
    Docker Container ID
    Docker User ID
    Docker Container Name{{ $server->username }}UUID / Docker Container ID{{ $server->uuid }}
    Service - {{ $server->option->service->name }} :: - {{ $server->option->name }} + {{ $server->nest->name }} :: + {{ $server->egg->name }}
    - - - - - - - - @foreach($services as $service) - - - - - - - - @endforeach -
    NameDescriptionOptionsPacksServers
    {{ $service->name }}{{ $service->description }}{{ $service->options_count }}{{ $service->packs_count }}{{ $service->servers_count }}
    -
    -
    -
    - -@endsection diff --git a/resources/themes/pterodactyl/admin/services/new.blade.php b/resources/themes/pterodactyl/admin/services/new.blade.php deleted file mode 100644 index aa3bd0b33..000000000 --- a/resources/themes/pterodactyl/admin/services/new.blade.php +++ /dev/null @@ -1,86 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - New Service -@endsection - -@section('content-header') -

    New ServiceConfigure a new service to deploy to all nodes.

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

    New Service

    -
    -
    -
    - -
    - -

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

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

    Services are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

    -
    -
    -
    - -
    - -

    The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.

    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/admin/services/view.blade.php b/resources/themes/pterodactyl/admin/services/view.blade.php deleted file mode 100644 index 64cf367c2..000000000 --- a/resources/themes/pterodactyl/admin/services/view.blade.php +++ /dev/null @@ -1,135 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Services → {{ $service->name }} -@endsection - -@section('content-header') -

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

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

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

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

    Services are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

    -
    -
    -
    - -
    - -

    The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.

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

    Configured Options

    -
    -
    - - - - - - - - @foreach($service->options as $option) - - - - - - - @endforeach -
    NameDescriptionTagServers
    {{ $option->name }}{!! $option->description !!}{{ $option->tag }}{{ $option->servers->count() }}
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/admin/settings.blade.php b/resources/themes/pterodactyl/admin/settings.blade.php deleted file mode 100644 index 7e13a7c4f..000000000 --- a/resources/themes/pterodactyl/admin/settings.blade.php +++ /dev/null @@ -1,84 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Settings -@endsection - -@section('content-header') -

    Panel SettingsConfigure Pterodactyl to your liking.

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

    Panel Settings

    -
    -
    -
    -
    -
    - -
    - -

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

    -
    -
    - {{--
    - -
    - -

    This is the default language that all clients will use unless they manually change it.

    -
    -
    --}} -
    -
    -
    -
    In order to modify your SMTP settings for sending mail you will need to run php artisan pterodactyl:mail in this project's root folder.
    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/admin/settings/advanced.blade.php b/resources/themes/pterodactyl/admin/settings/advanced.blade.php new file mode 100644 index 000000000..5a7b4d724 --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/advanced.blade.php @@ -0,0 +1,117 @@ +@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.

    +
    +
    +
    +
    +
    +
    +
    +

    Console

    +
    +
    +
    +
    + +
    + +

    The number of messages to be pushed to the console per frequency tick.

    +
    +
    +
    + +
    + +

    The amount of time in milliseconds between each console message sending tick.

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +@endsection diff --git a/resources/themes/pterodactyl/admin/settings/index.blade.php b/resources/themes/pterodactyl/admin/settings/index.blade.php new file mode 100644 index 000000000..62ef09631 --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/index.blade.php @@ -0,0 +1,75 @@ +@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/themes/pterodactyl/admin/settings/mail.blade.php b/resources/themes/pterodactyl/admin/settings/mail.blade.php new file mode 100644 index 000000000..40403993f --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/mail.blade.php @@ -0,0 +1,108 @@ +@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:encryption', config('mail.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 diff --git a/resources/themes/pterodactyl/admin/users/index.blade.php b/resources/themes/pterodactyl/admin/users/index.blade.php index 29fae6810..dd95f222b 100644 --- a/resources/themes/pterodactyl/admin/users/index.blade.php +++ b/resources/themes/pterodactyl/admin/users/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -53,13 +39,13 @@ - + - - + + @@ -67,7 +53,7 @@ @foreach ($users as $user) - + - + @can('reset-db-password', $server) - @foreach ($server->allocations as $allocation) + @foreach ($allocations as $allocation) @@ -74,6 +60,9 @@
    ID - Email + IDEmail Client Name Username 2FAServers OwnedCan AccessServers OwnedCan Access
    {{ $user->id }}{{ $user->email }}{{ $user->email }} @if($user->root_admin)@endif {{ $user->name_last }}, {{ $user->name_first }} {{ $user->username }} diff --git a/resources/themes/pterodactyl/admin/users/new.blade.php b/resources/themes/pterodactyl/admin/users/new.blade.php index 3debd78ee..4f4da9bd2 100644 --- a/resources/themes/pterodactyl/admin/users/new.blade.php +++ b/resources/themes/pterodactyl/admin/users/new.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.admin') @section('title') @@ -65,6 +51,17 @@ +
    + +
    + +

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

    +
    +
    +
    + +
    + +

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

    +
    +
    @@ -79,10 +77,11 @@
    -
    - +
    +
    +

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

    @@ -98,56 +97,60 @@

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

    +
    + + +

    If checked, any errors thrown while revoking keys across nodes will be ignored. You should avoid this checkbox if possible as any non-revoked keys could continue to be active for up to 24 hours after this account is changed. If you are needing to revoke account permissions immediately and are facing node issues, you should check this box and then restart any nodes that failed to be updated to clear out any stored keys.

    +
    -
    -
    -
    -

    Associated Servers

    -
    -
    - - - - - - - - - - - - - @foreach($user->setAccessLevel('subuser')->access()->get() as $server) - - - - - - - - - @endforeach - -
    IdentifierServer NameAccessNode
    {{ $server->uuidShort }}{{ $server->name }} - @if($server->owner_id === $user->id) - Owner - @else - Subuser - @endif - {{ $server->node->name }}@if($server->suspended === 0)Active@elseSuspended@endif
    -
    - -
    -
    + {{--
    --}} + {{--
    --}} + {{--
    --}} + {{--

    Associated Servers

    --}} + {{--
    --}} + {{--
    --}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{--@foreach($user->setAccessLevel('subuser')->access()->get() as $server)--}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{--@endforeach--}} + {{----}} + {{--
    IdentifierServer NameAccessNode
    {{ $server->uuidShort }}{{ $server->name }}--}} + {{--@if($server->owner_id === $user->id)--}} + {{--Owner--}} + {{--@else--}} + {{--Subuser--}} + {{--@endif--}} + {{--{{ $server->node->name }}@if($server->suspended === 0)Active@elseSuspended@endif
    --}} + {{--
    --}} + {{--
    --}} + {{--
    --}}
    diff --git a/resources/themes/pterodactyl/auth/login.blade.php b/resources/themes/pterodactyl/auth/login.blade.php index 95e0461b5..e81af8d31 100644 --- a/resources/themes/pterodactyl/auth/login.blade.php +++ b/resources/themes/pterodactyl/auth/login.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.auth') @section('title') @@ -24,49 +10,55 @@ @endsection @section('content') -
    {{ $database->database }} {{ $database->username }}{{ Crypt::decrypt($database->password) }} + + •••••••• + + + {{ $database->host->host }}:{{ $database->host->port }} @@ -69,7 +67,7 @@ @else
    -
    +
    @lang('server.config.database.no_dbs') @if(Auth::user()->root_admin === 1) @endsection diff --git a/resources/themes/pterodactyl/server/files/add.blade.php b/resources/themes/pterodactyl/server/files/add.blade.php index cbf468a25..fd6bf7362 100644 --- a/resources/themes/pterodactyl/server/files/add.blade.php +++ b/resources/themes/pterodactyl/server/files/add.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') diff --git a/resources/themes/pterodactyl/server/files/edit.blade.php b/resources/themes/pterodactyl/server/files/edit.blade.php index 0d29e2c6c..782b8d366 100644 --- a/resources/themes/pterodactyl/server/files/edit.blade.php +++ b/resources/themes/pterodactyl/server/files/edit.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -40,7 +26,7 @@ @@ -49,7 +35,7 @@
    diff --git a/resources/themes/pterodactyl/server/files/index.blade.php b/resources/themes/pterodactyl/server/files/index.blade.php index 9dc69958f..6b970ad70 100644 --- a/resources/themes/pterodactyl/server/files/index.blade.php +++ b/resources/themes/pterodactyl/server/files/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') diff --git a/resources/themes/pterodactyl/server/files/list.blade.php b/resources/themes/pterodactyl/server/files/list.blade.php index 989671c76..bbe2b24d8 100644 --- a/resources/themes/pterodactyl/server/files/list.blade.php +++ b/resources/themes/pterodactyl/server/files/list.blade.php @@ -1,26 +1,20 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}}

    /home/container{{ $directory['header'] }}

    +
    + + +
    @@ -38,13 +32,13 @@ - - + @@ -70,7 +64,7 @@ @endif @foreach ($folders as $folder) - + @@ -85,12 +79,12 @@ {{ $carbon->diffForHumans() }} @endif - + @endforeach @foreach ($files as $file) - - + @endforeach diff --git a/resources/themes/pterodactyl/server/index.blade.php b/resources/themes/pterodactyl/server/index.blade.php index 0a38e4ce3..9cb4ba4a9 100644 --- a/resources/themes/pterodactyl/server/index.blade.php +++ b/resources/themes/pterodactyl/server/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -44,7 +30,7 @@
    -
    {{ $server->username }}:~$
    +
    container:~/$
    @@ -93,7 +79,7 @@ {!! Theme::js('js/frontend/console.js') !!} {!! Theme::js('vendor/chartjs/chart.min.js') !!} {!! Theme::js('vendor/jquery/date-format.min.js') !!} - @if($server->service->folder === 'minecraft') + @if($server->nest->name === 'Minecraft' && $server->nest->author === 'support@pterodactyl.io') {!! Theme::js('js/plugins/minecraft/eula.js') !!} @endif @endsection diff --git a/resources/themes/pterodactyl/server/schedules/index.blade.php b/resources/themes/pterodactyl/server/schedules/index.blade.php new file mode 100644 index 000000000..b51ee089c --- /dev/null +++ b/resources/themes/pterodactyl/server/schedules/index.blade.php @@ -0,0 +1,96 @@ +{{-- 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.master') + +@section('title') + @lang('server.schedule.header') +@endsection + +@section('content-header') +

    @lang('server.schedule.header')@lang('server.schedule.header_sub')

    + +@endsection + +@section('content') +
    +
    +
    +
    +

    @lang('server.schedule.current')

    + +
    +
    +
    - + + @lang('server.files.file_name')
    {{ $folder['entry'] }}
    + {{-- oh boy --}} @if(in_array($file['mime'], [ 'application/x-7z-compressed', @@ -143,7 +137,7 @@ @if(in_array($file['mime'], $editableMime)) @can('edit-files', $server) - {{ $file['entry'] }} + {{ $file['entry'] }} @else {{ $file['entry'] }} @endcan @@ -162,7 +156,7 @@ {{ $carbon->diffForHumans() }} @endif
    + + + + + + + + + + @foreach($schedules as $schedule) + is_active)class="muted muted-hover"@endif> + + + + + + + + @endforeach + +
    @lang('strings.name')@lang('strings.queued')@lang('strings.tasks')@lang('strings.last_run')@lang('strings.next_run')
    + @can('edit-schedule', $server) + {{ $schedule->name }} + @else + {{ $schedule->name ?? trans('server.schedule.unnamed') }} + @endcan + + @if ($schedule->is_processing) + @lang('strings.yes') + @else + @lang('strings.no') + @endif + {{ $schedule->tasks_count }} + @if($schedule->last_run_at) + {{ Carbon::parse($schedule->last_run_at)->toDayDateTimeString() }}
    ({{ Carbon::parse($schedule->last_run_at)->diffForHumans() }}) + @else + @lang('strings.not_run_yet') + @endif +
    + @if($schedule->is_active) + {{ Carbon::parse($schedule->next_run_at)->toDayDateTimeString() }}
    ({{ Carbon::parse($schedule->next_run_at)->diffForHumans() }}) + @else + n/a + @endif +
    + @can('delete-schedule', $server) + + @endcan + @can('toggle-schedule', $server) + + + @endcan +
    +
    +
    +
    + +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/frontend/server.socket.js') !!} + {!! Theme::js('js/frontend/tasks/management-actions.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/server/schedules/new.blade.php b/resources/themes/pterodactyl/server/schedules/new.blade.php new file mode 100644 index 000000000..bb925c259 --- /dev/null +++ b/resources/themes/pterodactyl/server/schedules/new.blade.php @@ -0,0 +1,145 @@ +{{-- 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.master') + +@section('title') + @lang('server.schedules.new.header') +@endsection + +@section('scripts') + {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} + {!! Theme::css('vendor/select2/select2.min.css') !!} + @parent +@endsection + +@section('content-header') +

    @lang('server.schedule.new.header')@lang('server.schedule.new.header_sub')

    + +@endsection + +@section('content') +
    +
    +
    +
    +
    +

    @lang('server.schedule.setup')

    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + @include('partials.schedules.task-template') + +
    +
    +
    +
    +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/frontend/server.socket.js') !!} + {!! Theme::js('vendor/select2/select2.full.min.js') !!} + {!! Theme::js('js/frontend/tasks/view-actions.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/server/schedules/view.blade.php b/resources/themes/pterodactyl/server/schedules/view.blade.php new file mode 100644 index 000000000..87b15ad5f --- /dev/null +++ b/resources/themes/pterodactyl/server/schedules/view.blade.php @@ -0,0 +1,163 @@ +{{-- 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.master') + +@section('title') + @lang('server.schedules.edit.header') +@endsection + +@section('scripts') + {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} + {!! Theme::css('vendor/select2/select2.min.css') !!} + @parent +@endsection + +@section('content-header') +

    @lang('server.schedule.manage.header'){{ $schedule->name }}

    + +@endsection + +@section('content') +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + @include('partials.schedules.task-template') + +
    +
    +
    +
    +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/frontend/server.socket.js') !!} + {!! Theme::js('vendor/select2/select2.full.min.js') !!} + {!! Theme::js('js/frontend/tasks/view-actions.js') !!} + +@endsection diff --git a/resources/themes/pterodactyl/server/settings/allocation.blade.php b/resources/themes/pterodactyl/server/settings/allocation.blade.php index a6a0c36ad..5e73ac5cc 100644 --- a/resources/themes/pterodactyl/server/settings/allocation.blade.php +++ b/resources/themes/pterodactyl/server/settings/allocation.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -49,7 +35,7 @@
    @lang('strings.port')
    {{ $allocation->ip }} @@ -64,9 +50,9 @@ {{ $allocation->port }} @if($allocation->id === $server->allocation_id) - @lang('strings.primary') + @lang('strings.primary') @else - @lang('strings.make_primary') + @lang('strings.make_primary') @endif
    +
    @@ -93,37 +82,39 @@ @parent {!! Theme::js('js/frontend/server.socket.js') !!} @endsection diff --git a/resources/themes/pterodactyl/server/settings/sftp.blade.php b/resources/themes/pterodactyl/server/settings/sftp.blade.php index d146a74be..5da21ef77 100644 --- a/resources/themes/pterodactyl/server/settings/sftp.blade.php +++ b/resources/themes/pterodactyl/server/settings/sftp.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -35,37 +21,7 @@ @section('content')
    -
    -
    -
    -

    @lang('server.config.sftp.change_pass')

    -
    - @can('reset-sftp', $server) -
    -
    -
    - -
    - -

    @lang('auth.password_requirements')

    -
    -
    -
    - -
    - @else -
    -
    -

    @lang('auth.not_authorized')

    -
    -
    - @endcan -
    -
    -
    +

    @lang('server.config.sftp.details')

    @@ -80,20 +36,12 @@
    - +
    - @can('view-sftp-password', $server) -
    - -
    - sftp_password))value="{{ Crypt::decrypt($server->sftp_password) }}"@endif /> -
    -
    - @endcan
    diff --git a/resources/themes/pterodactyl/server/settings/startup.blade.php b/resources/themes/pterodactyl/server/settings/startup.blade.php index 021f540b8..594dae4d2 100644 --- a/resources/themes/pterodactyl/server/settings/startup.blade.php +++ b/resources/themes/pterodactyl/server/settings/startup.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -35,26 +21,20 @@ @section('content')
    -
    -
    -
    -
    -

    @lang('server.config.startup.command')

    +
    +
    +
    +

    @lang('server.config.startup.command')

    +
    +
    +
    +
    -
    -
    - -
    -
    - @can('edit-startup', $server) - - @endcan
    - @can('edit-startup', $server) +
    + @can('edit-startup', $server) + @foreach($variables as $v)
    @@ -64,11 +44,11 @@
    user_editable) - name="env_{{ $v->id }}" + name="environment[{{ $v->env_variable }}]" @else readonly @endif - class="form-control" type="text" value="{{ old('env_' . $v->id, $v->server_set_value) }}" /> + class="form-control" type="text" value="{{ old('environment.' . $v->env_variable, $server_values[$v->env_variable]) }}" />

    {{ $v->description }}

    @if($v->required && $v->user_editable ) @@ -82,14 +62,22 @@

    @endforeach - @endcan - +
    +
    + +
    +
    + + @endcan
    @endsection diff --git a/resources/themes/pterodactyl/server/tasks/index.blade.php b/resources/themes/pterodactyl/server/tasks/index.blade.php deleted file mode 100644 index 4ed9654cb..000000000 --- a/resources/themes/pterodactyl/server/tasks/index.blade.php +++ /dev/null @@ -1,103 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.master') - -@section('title') - @lang('server.tasks.header') -@endsection - -@section('content-header') -

    @lang('server.tasks.header')@lang('server.tasks.header_sub')

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

    @lang('server.tasks.current')

    - -
    -
    - - - - - - - - - - - - @foreach($tasks as $task) - active)class="muted muted-hover"@endif> - - - - - - @can('delete-task', $server) - - @endcan - @can('toggle-task', $server) - - @endcan - - @endforeach - - -
    @lang('strings.action')@lang('strings.data')@lang('strings.queued')@lang('strings.last_run')@lang('strings.next_run')
    {{ $actions[$task->action] }}{{ $task->data }} - @if ($task->queued) - @lang('strings.yes') - @else - @lang('strings.no') - @endif - - @if($task->last_run) - {{ Carbon::parse($task->last_run)->toDayDateTimeString() }}
    ({{ Carbon::parse($task->last_run)->diffForHumans() }}) - @else - @lang('strings.not_run_yet') - @endif -
    - @if($task->active !== 0) - {{ Carbon::parse($task->next_run)->toDayDateTimeString() }}
    ({{ Carbon::parse($task->next_run)->diffForHumans() }}) - @else - n/a - @endif -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('js/frontend/tasks.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/tasks/new.blade.php b/resources/themes/pterodactyl/server/tasks/new.blade.php deleted file mode 100644 index 045d2fe8d..000000000 --- a/resources/themes/pterodactyl/server/tasks/new.blade.php +++ /dev/null @@ -1,208 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.master') - -@section('title') - @lang('server.tasks.new.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

    @lang('server.tasks.new.header')@lang('server.tasks.new.header_sub')

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

    @lang('server.tasks.new.day_of_week')

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

    @lang('server.tasks.new.day_of_month')

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

    @lang('server.tasks.new.hour')

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

    @lang('server.tasks.new.minute')

    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - - @lang('server.tasks.new.payload_help') -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/users/index.blade.php b/resources/themes/pterodactyl/server/users/index.blade.php index a11261e5a..1cb88233f 100644 --- a/resources/themes/pterodactyl/server/users/index.blade.php +++ b/resources/themes/pterodactyl/server/users/index.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -71,14 +57,14 @@ {{ $subuser->user->created_at }} @can('view-subuser', $server) - + @endcan @can('delete-subuser', $server) - + @@ -112,9 +98,9 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('server.subusers.delete', { + url: Router.route('server.subusers.view', { server: Pterodactyl.server.uuidShort, - id: self.data('id'), + subuser: self.data('id'), }), headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), diff --git a/resources/themes/pterodactyl/server/users/new.blade.php b/resources/themes/pterodactyl/server/users/new.blade.php index e77144232..81231a704 100644 --- a/resources/themes/pterodactyl/server/users/new.blade.php +++ b/resources/themes/pterodactyl/server/users/new.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -44,7 +30,7 @@
    {!! csrf_field() !!} - +

    @lang('server.users.new.email_help')

    diff --git a/resources/themes/pterodactyl/server/users/view.blade.php b/resources/themes/pterodactyl/server/users/view.blade.php index 73fc359fb..f175bb44e 100644 --- a/resources/themes/pterodactyl/server/users/view.blade.php +++ b/resources/themes/pterodactyl/server/users/view.blade.php @@ -1,22 +1,8 @@ +{{-- Pterodactyl - Panel --}} {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} @extends('layouts.master') @section('title') @@ -35,7 +21,7 @@ @section('content') @can('edit-subuser', $server) - + @endcan
    + {!! method_field('PATCH') !!}
    @endcan diff --git a/resources/themes/pterodactyl/vendor/.gitkeep b/resources/themes/pterodactyl/vendor/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/resources/themes/pterodactyl/vendor/notifications/email-plain.blade.php b/resources/themes/pterodactyl/vendor/notifications/email-plain.blade.php index 875095895..acefa6523 100644 --- a/resources/themes/pterodactyl/vendor/notifications/email-plain.blade.php +++ b/resources/themes/pterodactyl/vendor/notifications/email-plain.blade.php @@ -19,4 +19,4 @@ if (! empty($outroLines)) { } echo 'Regards,', "\n"; -echo Settings::get('company'), "\n"; +echo config('app.name'), "\n"; diff --git a/resources/themes/pterodactyl/vendor/notifications/email.blade.php b/resources/themes/pterodactyl/vendor/notifications/email.blade.php index 855286b63..94f86a960 100644 --- a/resources/themes/pterodactyl/vendor/notifications/email.blade.php +++ b/resources/themes/pterodactyl/vendor/notifications/email.blade.php @@ -71,7 +71,7 @@ $style = [ - {{ Settings::get('company') }} + {{ config('app.name') }} @@ -140,7 +140,7 @@ $style = [

    - Regards,
    {{ Settings::get('company') }} + Regards,
    {{ config('app.name') }}

    @@ -176,7 +176,7 @@ $style = [

    © {{ date('Y') }} - {{ Settings::get('company') }}. + {{ config('app.name') }}. All rights reserved.

    diff --git a/resources/themes/pterodactyl/vendor/pagination/bootstrap-4.blade.php b/resources/themes/pterodactyl/vendor/pagination/bootstrap-4.blade.php deleted file mode 100644 index 9d80428cc..000000000 --- a/resources/themes/pterodactyl/vendor/pagination/bootstrap-4.blade.php +++ /dev/null @@ -1,36 +0,0 @@ -@if ($paginator->count() > 1) -
      - - @if ($paginator->onFirstPage()) -
    • «
    • - @else -
    • - @endif - - - @foreach ($elements as $element) - - @if (is_string($element)) -
    • {{ $element }}
    • - @endif - - - @if (is_array($element)) - @foreach ($element as $page => $url) - @if ($page == $paginator->currentPage()) -
    • {{ $page }}
    • - @else -
    • {{ $page }}
    • - @endif - @endforeach - @endif - @endforeach - - - @if ($paginator->hasMorePages()) -
    • - @else -
    • »
    • - @endif -
    -@endif diff --git a/resources/themes/pterodactyl/vendor/pagination/default.blade.php b/resources/themes/pterodactyl/vendor/pagination/default.blade.php index 26e56994f..1ecfac985 100644 --- a/resources/themes/pterodactyl/vendor/pagination/default.blade.php +++ b/resources/themes/pterodactyl/vendor/pagination/default.blade.php @@ -1,5 +1,5 @@ @if ($paginator->lastPage() > 1) -
      +
        @if ($paginator->onFirstPage()) {{--
      • «
      • --}} diff --git a/resources/themes/pterodactyl/vendor/pagination/simple-bootstrap-4.blade.php b/resources/themes/pterodactyl/vendor/pagination/simple-bootstrap-4.blade.php deleted file mode 100644 index 4b14efeb5..000000000 --- a/resources/themes/pterodactyl/vendor/pagination/simple-bootstrap-4.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -@if ($paginator->count() > 1) -
          - - @if ($paginator->onFirstPage()) -
        • «
        • - @else -
        • - @endif - - - @if ($paginator->hasMorePages()) -
        • - @else -
        • »
        • - @endif -
        -@endif diff --git a/resources/themes/pterodactyl/vendor/pagination/simple-default.blade.php b/resources/themes/pterodactyl/vendor/pagination/simple-default.blade.php deleted file mode 100644 index a45097ee2..000000000 --- a/resources/themes/pterodactyl/vendor/pagination/simple-default.blade.php +++ /dev/null @@ -1,17 +0,0 @@ -@if ($paginator->count() > 1) -
          - - @if ($paginator->onFirstPage()) -
        • «
        • - @else -
        • - @endif - - - @if ($paginator->hasMorePages()) -
        • - @else -
        • »
        • - @endif -
        -@endif diff --git a/routes/admin.php b/routes/admin.php index 7b6c459ee..7dfa94f09 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -1,27 +1,23 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -Route::get('/', 'BaseController@getIndex')->name('admin.index'); + +Route::get('/', 'BaseController@index')->name('admin.index'); + +/* +|-------------------------------------------------------------------------- +| Location Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /admin/api +| +*/ +Route::group(['prefix' => 'api'], function () { + Route::get('/', 'ApiController@index')->name('admin.api.index'); + Route::get('/new', 'ApiController@create')->name('admin.api.new'); + + Route::post('/new', 'ApiController@store'); + + Route::delete('/revoke/{identifier}', 'ApiController@delete')->name('admin.api.delete'); +}); /* |-------------------------------------------------------------------------- @@ -33,10 +29,10 @@ Route::get('/', 'BaseController@getIndex')->name('admin.index'); */ Route::group(['prefix' => 'locations'], function () { Route::get('/', 'LocationController@index')->name('admin.locations'); - Route::get('/view/{id}', 'LocationController@view')->name('admin.locations.view'); + Route::get('/view/{location}', 'LocationController@view')->name('admin.locations.view'); Route::post('/', 'LocationController@create'); - Route::post('/view/{id}', 'LocationController@update'); + Route::patch('/view/{location}', 'LocationController@update'); }); /* @@ -49,10 +45,11 @@ Route::group(['prefix' => 'locations'], function () { */ Route::group(['prefix' => 'databases'], function () { Route::get('/', 'DatabaseController@index')->name('admin.databases'); - Route::get('/view/{id}', 'DatabaseController@view')->name('admin.databases.view'); + Route::get('/view/{host}', 'DatabaseController@view')->name('admin.databases.view'); Route::post('/', 'DatabaseController@create'); - Route::post('/view/{id}', 'DatabaseController@update'); + Route::patch('/view/{host}', 'DatabaseController@update'); + Route::delete('/view/{host}', 'DatabaseController@delete'); }); /* @@ -64,9 +61,13 @@ Route::group(['prefix' => 'databases'], function () { | */ Route::group(['prefix' => 'settings'], function () { - Route::get('/', 'BaseController@getSettings')->name('admin.settings'); + 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('/', 'BaseController@postSettings'); + Route::patch('/', 'Settings\IndexController@update'); + Route::patch('/mail', 'Settings\MailController@update'); + Route::patch('/advanced', 'Settings\AdvancedController@update'); }); /* @@ -81,12 +82,12 @@ 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/{id}', 'UserController@view')->name('admin.users.view'); + Route::get('/view/{user}', 'UserController@view')->name('admin.users.view'); Route::post('/new', 'UserController@store'); - Route::post('/view/{id}', 'UserController@update'); + Route::patch('/view/{user}', 'UserController@update'); - Route::delete('/view/{id}', 'UserController@delete'); + Route::delete('/view/{user}', 'UserController@delete'); }); /* @@ -100,30 +101,28 @@ Route::group(['prefix' => 'users'], function () { Route::group(['prefix' => 'servers'], function () { Route::get('/', 'ServersController@index')->name('admin.servers'); Route::get('/new', 'ServersController@create')->name('admin.servers.new'); - Route::get('/view/{id}', 'ServersController@viewIndex')->name('admin.servers.view'); - Route::get('/view/{id}/details', 'ServersController@viewDetails')->name('admin.servers.view.details'); - Route::get('/view/{id}/build', 'ServersController@viewBuild')->name('admin.servers.view.build'); - Route::get('/view/{id}/startup', 'ServersController@viewStartup')->name('admin.servers.view.startup'); - Route::get('/view/{id}/database', 'ServersController@viewDatabase')->name('admin.servers.view.database'); - Route::get('/view/{id}/manage', 'ServersController@viewManage')->name('admin.servers.view.manage'); - Route::get('/view/{id}/delete', 'ServersController@viewDelete')->name('admin.servers.view.delete'); + Route::get('/view/{server}', 'ServersController@viewIndex')->name('admin.servers.view'); + Route::get('/view/{server}/details', 'ServersController@viewDetails')->name('admin.servers.view.details'); + Route::get('/view/{server}/build', 'ServersController@viewBuild')->name('admin.servers.view.build'); + Route::get('/view/{server}/startup', 'ServersController@viewStartup')->name('admin.servers.view.startup'); + Route::get('/view/{server}/database', 'ServersController@viewDatabase')->name('admin.servers.view.database'); + Route::get('/view/{server}/manage', 'ServersController@viewManage')->name('admin.servers.view.manage'); + Route::get('/view/{server}/delete', 'ServersController@viewDelete')->name('admin.servers.view.delete'); Route::post('/new', 'ServersController@store'); - Route::post('/new/nodes', 'ServersController@nodes')->name('admin.servers.new.nodes'); - Route::post('/view/{id}/details', 'ServersController@setDetails'); - Route::post('/view/{id}/details/container', 'ServersController@setContainer')->name('admin.servers.view.details.container'); - Route::post('/view/{id}/build', 'ServersController@updateBuild'); - Route::post('/view/{id}/startup', 'ServersController@saveStartup'); - Route::post('/view/{id}/database', 'ServersController@newDatabase'); - Route::post('/view/{id}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle'); - Route::post('/view/{id}/manage/rebuild', 'ServersController@rebuildContainer')->name('admin.servers.view.manage.rebuild'); - Route::post('/view/{id}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension'); - Route::post('/view/{id}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{id}/delete', 'ServersController@delete'); + 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}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle'); + Route::post('/view/{server}/manage/rebuild', 'ServersController@rebuildContainer')->name('admin.servers.view.manage.rebuild'); + 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}/delete', 'ServersController@delete'); - Route::patch('/view/{id}/database', 'ServersController@resetDatabasePassword'); + Route::patch('/view/{server}/details', 'ServersController@setDetails'); + Route::patch('/view/{server}/database', 'ServersController@resetDatabasePassword'); - Route::delete('/view/{id}/database/{database}/delete', 'ServersController@deleteDatabase')->name('admin.servers.view.database.delete'); + Route::delete('/view/{server}/database/{database}/delete', 'ServersController@deleteDatabase')->name('admin.servers.view.database.delete'); }); /* @@ -137,50 +136,57 @@ Route::group(['prefix' => 'servers'], function () { Route::group(['prefix' => 'nodes'], function () { Route::get('/', 'NodesController@index')->name('admin.nodes'); Route::get('/new', 'NodesController@create')->name('admin.nodes.new'); - Route::get('/view/{id}', 'NodesController@viewIndex')->name('admin.nodes.view'); - Route::get('/view/{id}/settings', 'NodesController@viewSettings')->name('admin.nodes.view.settings'); - Route::get('/view/{id}/configuration', 'NodesController@viewConfiguration')->name('admin.nodes.view.configuration'); - Route::get('/view/{id}/allocation', 'NodesController@viewAllocation')->name('admin.nodes.view.allocation'); - Route::get('/view/{id}/servers', 'NodesController@viewServers')->name('admin.nodes.view.servers'); - Route::get('/view/{id}/settings/token', 'NodesController@setToken')->name('admin.nodes.view.configuration.token'); + Route::get('/view/{node}', 'NodesController@viewIndex')->name('admin.nodes.view'); + Route::get('/view/{node}/settings', 'NodesController@viewSettings')->name('admin.nodes.view.settings'); + Route::get('/view/{node}/configuration', 'NodesController@viewConfiguration')->name('admin.nodes.view.configuration'); + Route::get('/view/{node}/allocation', 'NodesController@viewAllocation')->name('admin.nodes.view.allocation'); + Route::get('/view/{node}/servers', 'NodesController@viewServers')->name('admin.nodes.view.servers'); + Route::get('/view/{node}/settings/token', 'NodesController@setToken')->name('admin.nodes.view.configuration.token'); Route::post('/new', 'NodesController@store'); - Route::post('/view/{id}/settings', 'NodesController@updateSettings'); - Route::post('/view/{id}/allocation', 'NodesController@createAllocation'); - Route::post('/view/{id}/allocation/remove', 'NodesController@allocationRemoveBlock')->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{id}/allocation/alias', 'NodesController@allocationSetAlias')->name('admin.nodes.view.allocation.setAlias'); + 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::delete('/view/{id}/delete', 'NodesController@delete')->name('admin.nodes.view.delete'); - Route::delete('/view/{id}/allocation/remove/{allocation}', 'NodesController@allocationRemoveSingle')->name('admin.nodes.view.allocation.removeSingle'); + 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'); }); /* |-------------------------------------------------------------------------- -| Service Controller Routes +| Nest Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /admin/services +| Endpoint: /admin/nests | */ -Route::group(['prefix' => 'services'], function () { - Route::get('/', 'ServiceController@index')->name('admin.services'); - Route::get('/new', 'ServiceController@create')->name('admin.services.new'); - Route::get('/view/{id}', 'ServiceController@view')->name('admin.services.view'); - Route::get('/view/{id}/functions', 'ServiceController@viewFunctions')->name('admin.services.view.functions'); - Route::get('/option/new', 'OptionController@create')->name('admin.services.option.new'); - Route::get('/option/{id}', 'OptionController@viewConfiguration')->name('admin.services.option.view'); - Route::get('/option/{id}/variables', 'OptionController@viewVariables')->name('admin.services.option.variables'); - Route::get('/option/{id}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts'); +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', 'ServiceController@store'); - Route::post('/view/{id}', 'ServiceController@edit'); - Route::post('/option/new', 'OptionController@store'); - Route::post('/option/{id}', 'OptionController@editConfiguration'); - Route::post('/option/{id}/scripts', 'OptionController@updateScripts'); - Route::post('/option/{id}/variables', 'OptionController@createVariable'); - Route::post('/option/{id}/variables/{variable}', 'OptionController@editVariable')->name('admin.services.option.variables.edit'); + 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::delete('/view/{id}', 'ServiceController@delete'); + 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'); }); /* @@ -195,9 +201,12 @@ Route::group(['prefix' => 'packs'], function () { Route::get('/', 'PackController@index')->name('admin.packs'); Route::get('/new', 'PackController@create')->name('admin.packs.new'); Route::get('/new/template', 'PackController@newTemplate')->name('admin.packs.new.template'); - Route::get('/view/{id}', 'PackController@view')->name('admin.packs.view'); + Route::get('/view/{pack}', 'PackController@view')->name('admin.packs.view'); Route::post('/new', 'PackController@store'); - Route::post('/view/{id}', 'PackController@update'); - Route::post('/view/{id}/export/{files?}', 'PackController@export')->name('admin.packs.view.export'); + Route::post('/view/{pack}/export/{files?}', 'PackController@export')->name('admin.packs.view.export'); + + Route::patch('/view/{pack}', 'PackController@update'); + + Route::delete('/view/{pack}', 'PackController@destroy'); }); diff --git a/routes/api-admin.php b/routes/api-admin.php deleted file mode 100644 index d5ca5b0b3..000000000 --- a/routes/api-admin.php +++ /dev/null @@ -1,112 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -Route::get('/', 'CoreController@index'); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/servers -| -*/ -Route::group(['prefix' => '/servers'], function () { - Route::get('/', 'ServerController@index'); - Route::get('/{id}', 'ServerController@view'); - - Route::post('/', 'ServerController@store'); - - Route::put('/{id}/details', 'ServerController@details'); - Route::put('/{id}/container', 'ServerController@container'); - Route::put('/{id}/build', 'ServerController@build'); - Route::put('/{id}/startup', 'ServerController@startup'); - - Route::patch('/{id}/install', 'ServerController@install'); - Route::patch('/{id}/rebuild', 'ServerController@rebuild'); - Route::patch('/{id}/suspend', 'ServerController@suspend'); - - Route::delete('/{id}', 'ServerController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/locations -| -*/ -Route::group(['prefix' => '/locations'], function () { - Route::get('/', 'LocationController@index'); -}); - -/* -|-------------------------------------------------------------------------- -| Node Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/nodes -| -*/ -Route::group(['prefix' => '/nodes'], function () { - Route::get('/', 'NodeController@index'); - Route::get('/{id}', 'NodeController@view'); - Route::get('/{id}/config', 'NodeController@viewConfig'); - - Route::post('/', 'NodeController@store'); - - Route::delete('/{id}', 'NodeController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| User Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/users -| -*/ -Route::group(['prefix' => '/users'], function () { - Route::get('/', 'UserController@index'); - Route::get('/{id}', 'UserController@view'); - - Route::post('/', 'UserController@store'); - - Route::put('/{id}', 'UserController@update'); - - Route::delete('/{id}', 'UserController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Service Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/services -| -*/ -Route::group(['prefix' => '/services'], function () { - Route::get('/', 'ServiceController@index'); - Route::get('/{id}', 'ServiceController@view'); -}); diff --git a/routes/api-application.php b/routes/api-application.php new file mode 100644 index 000000000..900de8c6a --- /dev/null +++ b/routes/api-application.php @@ -0,0 +1,120 @@ + '/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::post('/', 'Users\UserController@store'); + Route::patch('/{user}', 'Users\UserController@update'); + + Route::delete('/{user}', 'Users\UserController@delete'); +}); + +/* +|-------------------------------------------------------------------------- +| Node Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/nodes +| +*/ +Route::group(['prefix' => '/nodes'], function () { + Route::get('/', 'Nodes\NodeController@index')->name('api.application.nodes'); + Route::get('/{node}', 'Nodes\NodeController@view')->name('api.application.nodes.view'); + + Route::post('/', 'Nodes\NodeController@store'); + Route::patch('/{node}', 'Nodes\NodeController@update'); + + Route::delete('/{node}', 'Nodes\NodeController@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'); + }); +}); + +/* +|-------------------------------------------------------------------------- +| Location Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/locations +| +*/ +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::post('/', 'Locations\LocationController@store'); + Route::patch('/{location}', 'Locations\LocationController@update'); + + Route::delete('/{location}', 'Locations\LocationController@delete'); +}); + +/* +|-------------------------------------------------------------------------- +| Server Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/servers +| +*/ +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::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::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('/{server}/rebuild', 'Servers\ServerManagementController@rebuild')->name('api.application.servers.rebuild'); + + Route::delete('/{server}', 'Servers\ServerController@delete'); + Route::delete('/{server}/{force?}', 'Servers\ServerController@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::post('/', 'Servers\DatabaseController@store'); + Route::post('/{database}/reset-password', 'Servers\DatabaseController@resetPassword'); + + Route::delete('/{database}', 'Servers\DatabaseController@delete'); + }); +}); + +/* +|-------------------------------------------------------------------------- +| Nest Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/nests +| +*/ +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'); + + // 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'); + }); +}); diff --git a/routes/api-remote.php b/routes/api-remote.php new file mode 100644 index 000000000..a06a72feb --- /dev/null +++ b/routes/api-remote.php @@ -0,0 +1,22 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ +Route::get('/authenticate/{token}', 'ValidateKeyController@index')->name('api.remote.authenticate'); + +Route::group(['prefix' => '/eggs'], function () { + Route::get('/', 'EggRetrievalController@index')->name('api.remote.eggs'); + Route::get('/{uuid}', 'EggRetrievalController@download')->name('api.remote.eggs.download'); +}); + +Route::group(['prefix' => '/scripts'], function () { + Route::get('/{uuid}', 'EggInstallController@index')->name('api.remote.scripts'); +}); + +Route::group(['prefix' => '/sftp'], function () { + Route::post('/', 'SftpController@index')->name('api.remote.sftp'); +}); diff --git a/routes/api.php b/routes/api.php index 9b2e01dfa..96dfe5dde 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,42 +1,27 @@ . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ -Route::get('/', 'CoreController@index')->name('api.user'); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/user/server/{server} -| -*/ -Route::group([ - 'prefix' => '/server/{server}', - 'middleware' => 'server', -], function () { - Route::get('/', 'ServerController@index')->name('api.user.server'); - - Route::post('/power', 'ServerController@power')->name('api.user.server.power'); - Route::post('/command', 'ServerController@command')->name('api.user.server.command'); -}); +//Route::get('/', 'CoreController@index')->name('api.user'); +// +///* +//|-------------------------------------------------------------------------- +//| Server Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/user/server/{server} +//| +//*/ +//Route::group([ +// 'prefix' => '/server/{server}', +// 'middleware' => 'server', +//], function () { +// Route::get('/', 'ServerController@index')->name('api.user.server'); +// +// Route::post('/power', 'ServerController@power')->name('api.user.server.power'); +// Route::post('/command', 'ServerController@command')->name('api.user.server.command'); +//}); diff --git a/routes/auth.php b/routes/auth.php index 256fd645e..f0ac210b5 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,34 +1,32 @@ . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -Route::get('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth'); -Route::get('/login', 'LoginController@showLoginForm')->name('auth.login'); -Route::get('/login/totp', 'LoginController@totp')->name('auth.totp'); -Route::get('/password', 'ForgotPasswordController@showLinkRequestForm')->name('auth.password'); -Route::get('/password/reset/{token}', 'ResetPasswordController@showResetForm')->name('auth.reset'); -Route::post('/login', 'LoginController@login')->middleware('recaptcha'); -Route::post('/login/totp', 'LoginController@totpCheckpoint'); -Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); -Route::post('/password/reset', 'ResetPasswordController@reset')->name('auth.reset.post')->middleware('recaptcha'); -Route::post('/password/reset/{token}', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); +/* +|-------------------------------------------------------------------------- +| Authentication Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /auth +| +*/ +Route::group(['middleware' => 'guest'], function () { + Route::get('/login', 'LoginController@showLoginForm')->name('auth.login'); + Route::get('/login/totp', 'LoginController@totp')->name('auth.totp'); + Route::get('/password', 'ForgotPasswordController@showLinkRequestForm')->name('auth.password'); + Route::get('/password/reset/{token}', 'ResetPasswordController@showResetForm')->name('auth.reset'); + + Route::post('/login', 'LoginController@login')->middleware('recaptcha'); + Route::post('/login/totp', 'LoginController@loginUsingTotp'); + Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); + Route::post('/password/reset', 'ResetPasswordController@reset')->name('auth.reset.post')->middleware('recaptcha'); + Route::post('/password/reset/{token}', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); +}); + +/* +|-------------------------------------------------------------------------- +| Routes Accessable only when logged in +|-------------------------------------------------------------------------- +| +| Endpoint: /auth +| +*/ +Route::get('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth'); diff --git a/routes/base.php b/routes/base.php index 4b06bb645..3947b7551 100644 --- a/routes/base.php +++ b/routes/base.php @@ -3,31 +3,12 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ Route::get('/', 'IndexController@getIndex')->name('index'); Route::get('/status/{server}', 'IndexController@status')->name('index.status'); -Route::get('/index', function () { - redirect()->route('index'); -}); - /* |-------------------------------------------------------------------------- | Account Controller Routes @@ -49,15 +30,16 @@ Route::group(['prefix' => 'account'], function () { | | Endpoint: /account/api | +| Temporarily Disabled */ -Route::group(['prefix' => 'account/api'], function () { - Route::get('/', 'APIController@index')->name('account.api'); - Route::get('/new', 'APIController@create')->name('account.api.new'); - - Route::post('/new', 'APIController@store'); - - Route::delete('/revoke/{key}', 'APIController@revoke')->name('account.api.revoke'); -}); +//Route::group(['prefix' => 'account/api'], function () { +// Route::get('/', 'AccountKeyController@index')->name('account.api'); +// Route::get('/new', 'AccountKeyController@create')->name('account.api.new'); +// +// Route::post('/new', 'AccountKeyController@store'); +// +// Route::delete('/revoke/{identifier}', 'AccountKeyController@revoke')->name('account.api.revoke'); +//}); /* |-------------------------------------------------------------------------- @@ -73,7 +55,7 @@ Route::group(['prefix' => 'account/security'], function () { Route::put('/totp', 'SecurityController@generateTotp')->name('account.security.totp'); - Route::post('/totp', 'SecurityController@setTotp'); + Route::post('/totp', 'SecurityController@setTotp')->name('account.security.totp.set'); - Route::delete('/totp', 'SecurityController@disableTotp'); + Route::delete('/totp', 'SecurityController@disableTotp')->name('account.security.totp.disable'); }); diff --git a/routes/daemon.php b/routes/daemon.php index 73876c46a..b74a005a7 100644 --- a/routes/daemon.php +++ b/routes/daemon.php @@ -3,29 +3,11 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ -Route::get('/services', 'ServiceController@listServices')->name('daemon.services'); -Route::get('/services/pull/{service}/{file}', 'ServiceController@pull')->name('daemon.pull'); Route::get('/packs/pull/{uuid}', 'PackController@pull')->name('daemon.pack.pull'); Route::get('/packs/pull/{uuid}/hash', 'PackController@hash')->name('daemon.pack.hash'); -Route::get('/details/option/{server}', 'OptionController@details')->name('daemon.option.details'); Route::get('/configure/{token}', 'ActionController@configuration')->name('daemon.configuration'); Route::post('/download', 'ActionController@authenticateDownload')->name('daemon.download'); diff --git a/routes/server.php b/routes/server.php index d446bf6a0..85283df9e 100644 --- a/routes/server.php +++ b/routes/server.php @@ -3,26 +3,11 @@ * Pterodactyl - Panel * Copyright (c) 2015 - 2017 Dane Everitt . * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT */ -Route::get('/', 'ServerController@getIndex')->name('server.index'); -Route::get('/console', 'ServerController@getConsole')->name('server.console'); +Route::get('/', 'ConsoleController@index')->name('server.index'); +Route::get('/console', 'ConsoleController@console')->name('server.console'); /* |-------------------------------------------------------------------------- @@ -33,13 +18,27 @@ Route::get('/console', 'ServerController@getConsole')->name('server.console'); | */ Route::group(['prefix' => 'settings'], function () { - Route::get('/databases', 'ServerController@getDatabases')->name('server.settings.databases'); - Route::get('/sftp', 'ServerController@getSFTP')->name('server.settings.sftp'); - Route::get('/startup', 'ServerController@getStartup')->name('server.settings.startup'); - Route::get('/allocation', 'ServerController@getAllocation')->name('server.settings.allocation'); + Route::get('/allocation', 'Settings\AllocationController@index')->name('server.settings.allocation'); + Route::patch('/allocation', 'Settings\AllocationController@update'); - Route::post('/sftp', 'ServerController@postSettingsSFTP'); - Route::post('/startup', 'ServerController@postSettingsStartup'); + Route::get('/sftp', 'Settings\SftpController@index')->name('server.settings.sftp'); + + Route::get('/startup', 'Settings\StartupController@index')->name('server.settings.startup'); + Route::patch('/startup', 'Settings\StartupController@update'); +}); + +/* +|-------------------------------------------------------------------------- +| Server Database Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /server/{server}/databases +| +*/ +Route::group(['prefix' => 'databases'], function () { + Route::get('/', 'DatabaseController@index')->name('server.databases.index'); + + Route::patch('/password', 'DatabaseController@update')->middleware('server..database')->name('server.databases.password'); }); /* @@ -51,17 +50,13 @@ Route::group(['prefix' => 'settings'], function () { | */ Route::group(['prefix' => 'files'], function () { - Route::get('/', 'ServerController@getFiles')->name('server.files.index'); - Route::get('/add', 'ServerController@getAddFile')->name('server.files.add'); - Route::get('/edit/{file}', 'ServerController@getEditFile') - ->name('server.files.edit') - ->where('file', '.*'); - Route::get('/download/{file}', 'ServerController@getDownloadFile') - ->name('server.files.edit') - ->where('file', '.*'); + Route::get('/', 'Files\FileActionsController@index')->name('server.files.index'); + Route::get('/add', 'Files\FileActionsController@create')->name('server.files.add'); + Route::get('/edit/{file}', 'Files\FileActionsController@view')->name('server.files.edit')->where('file', '.*'); + Route::get('/download/{file}', 'Files\DownloadController@index')->name('server.files.edit')->where('file', '.*'); - Route::post('/directory-list', 'AjaxController@postDirectoryList')->name('server.files.directory-list'); - Route::post('/save', 'AjaxController@postSaveFile')->name('server.files.save'); + Route::post('/directory-list', 'Files\RemoteRequestController@directory')->name('server.files.directory-list'); + Route::post('/save', 'Files\RemoteRequestController@store')->name('server.files.save'); }); /* @@ -75,12 +70,13 @@ Route::group(['prefix' => 'files'], function () { Route::group(['prefix' => 'users'], function () { Route::get('/', 'SubuserController@index')->name('server.subusers'); Route::get('/new', 'SubuserController@create')->name('server.subusers.new'); - Route::get('/view/{id}', 'SubuserController@view')->name('server.subusers.view'); - Route::post('/new', 'SubuserController@store'); - Route::post('/view/{id}', 'SubuserController@update'); - Route::delete('/delete/{id}', 'SubuserController@delete')->name('server.subusers.delete'); + Route::group(['middleware' => 'server..subuser'], function () { + Route::get('/view/{subuser}', 'SubuserController@view')->name('server.subusers.view'); + Route::patch('/view/{subuser}', 'SubuserController@update'); + Route::delete('/view/{subuser}', 'SubuserController@delete'); + }); }); /* @@ -91,25 +87,18 @@ Route::group(['prefix' => 'users'], function () { | Endpoint: /server/{server}/tasks | */ -Route::group(['prefix' => 'tasks'], function () { - Route::get('/', 'TaskController@index')->name('server.tasks'); - Route::get('/new', 'TaskController@create')->name('server.tasks.new'); +Route::group(['prefix' => 'schedules'], function () { + Route::get('/', 'Tasks\TaskManagementController@index')->name('server.schedules'); + Route::get('/new', 'Tasks\TaskManagementController@create')->name('server.schedules.new'); + Route::post('/new', 'Tasks\TaskManagementController@store'); - Route::post('/new', 'TaskController@store'); - Route::post('/toggle/{id}', 'TaskController@toggle')->name('server.tasks.toggle'); + Route::group(['middleware' => 'server..schedule'], function () { + Route::get('/view/{schedule}', 'Tasks\TaskManagementController@view')->name('server.schedules.view'); - Route::delete('/delete/{id}', 'TaskController@delete')->name('server.tasks.delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Server Ajax Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /server/{server}/ajax -| -*/ -Route::group(['prefix' => 'ajax'], function () { - Route::post('/set-primary', 'AjaxController@postSetPrimary')->name('server.ajax.set-primary'); - Route::post('/settings/reset-database-password', 'AjaxController@postResetDatabasePassword')->name('server.ajax.reset-database-password'); + Route::patch('/view/{schedule}', 'Tasks\TaskManagementController@update'); + Route::post('/view/{schedule}/toggle', 'Tasks\ActionController@toggle')->name('server.schedules.toggle'); + Route::post('/view/{schedule}/trigger', 'Tasks\ActionController@trigger')->name('server.schedules.trigger'); + + Route::delete('/view/{schedule}', 'Tasks\TaskManagementController@delete'); + }); }); diff --git a/tests/Assertions/CommandAssertionsTrait.php b/tests/Assertions/CommandAssertionsTrait.php new file mode 100644 index 000000000..bf88daa9f --- /dev/null +++ b/tests/Assertions/CommandAssertionsTrait.php @@ -0,0 +1,26 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Assertions; + +use PHPUnit\Framework\Assert; + +trait CommandAssertionsTrait +{ + /** + * Assert that an output table contains a value. + * + * @param mixed $string + * @param string $display + */ + public function assertTableContains($string, $display) + { + Assert::assertRegExp('/\|(\s+)' . preg_quote($string) . '(\s+)\|/', $display, 'Assert that a response table contains a value.'); + } +} diff --git a/tests/Assertions/ControllerAssertionsTrait.php b/tests/Assertions/ControllerAssertionsTrait.php new file mode 100644 index 000000000..16cfcdd93 --- /dev/null +++ b/tests/Assertions/ControllerAssertionsTrait.php @@ -0,0 +1,207 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Assertions; + +use Illuminate\View\View; +use Illuminate\Http\Response; +use PHPUnit\Framework\Assert; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; + +trait ControllerAssertionsTrait +{ + /** + * Assert that a response is an instance of Illuminate View. + * + * @param mixed $response + */ + public function assertIsViewResponse($response) + { + Assert::assertInstanceOf(View::class, $response); + } + + /** + * Assert that a response is an instance of Illuminate Redirect Response. + * + * @param mixed $response + */ + public function assertIsRedirectResponse($response) + { + Assert::assertInstanceOf(RedirectResponse::class, $response); + } + + /** + * Assert that a response is an instance of Illuminate Json Response. + * + * @param mixed $response + */ + public function assertIsJsonResponse($response) + { + Assert::assertInstanceOf(JsonResponse::class, $response); + } + + /** + * Assert that a response is an instance of Illuminate Response. + * + * @param mixed $response + */ + public function assertIsResponse($response) + { + Assert::assertInstanceOf(Response::class, $response); + } + + /** + * Assert that a view name equals the passed name. + * + * @param string $name + * @param mixed $view + */ + public function assertViewNameEquals($name, $view) + { + Assert::assertEquals($name, $view->getName()); + } + + /** + * Assert that a view name does not equal a provided name. + * + * @param string $name + * @param mixed $view + */ + public function assertViewNameNotEquals($name, $view) + { + Assert::assertNotEquals($name, $view->getName()); + } + + /** + * Assert that a view has an attribute passed into it. + * + * @param string $attribute + * @param mixed $view + */ + public function assertViewHasKey($attribute, $view) + { + if (str_contains($attribute, '.')) { + Assert::assertNotEquals( + '__TEST__FAIL', + array_get($view->getData(), $attribute, '__TEST__FAIL') + ); + } else { + Assert::assertArrayHasKey($attribute, $view->getData()); + } + } + + /** + * Assert that a view does not have a specific attribute passed in. + * + * @param string $attribute + * @param mixed $view + */ + public function assertViewNotHasKey($attribute, $view) + { + if (str_contains($attribute, '.')) { + Assert::assertEquals( + '__TEST__PASS', + array_get($view->getData(), $attribute, '__TEST__PASS') + ); + } else { + Assert::assertArrayNotHasKey($attribute, $view->getData()); + } + } + + /** + * Assert that a view attribute equals a given parameter. + * + * @param string $attribute + * @param mixed $value + * @param mixed $view + */ + public function assertViewKeyEquals($attribute, $value, $view) + { + Assert::assertEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); + } + + /** + * Assert that a view attribute does not equal a given parameter. + * + * @param string $attribute + * @param mixed $value + * @param mixed $view + */ + public function assertViewKeyNotEquals($attribute, $value, $view) + { + Assert::assertNotEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); + } + + /** + * Assert that a route redirect equals a given route name. + * + * @param string $route + * @param mixed $response + * @param array $args + */ + public function assertRedirectRouteEquals($route, $response, array $args = []) + { + Assert::assertEquals(route($route, $args), $response->getTargetUrl()); + } + + /** + * Assert that a route redirect URL equals as passed URL. + * + * @param string $url + * @param mixed $response + */ + public function assertRedirectUrlEquals($url, $response) + { + Assert::assertEquals($url, $response->getTargetUrl()); + } + + /** + * Assert that a response code equals a given code. + * + * @param int $code + * @param mixed $response + */ + public function assertResponseCodeEquals($code, $response) + { + Assert::assertEquals($code, $response->getStatusCode()); + } + + /** + * Assert that a response code does not equal a given code. + * + * @param int $code + * @param mixed $response + */ + public function assertResponseCodeNotEquals($code, $response) + { + Assert::assertNotEquals($code, $response->getStatusCode()); + } + + /** + * Assert that a response is in a JSON format. + * + * @param mixed $response + */ + public function assertResponseHasJsonHeaders($response) + { + Assert::assertEquals('application/json', $response->headers->get('content-type')); + } + + /** + * Assert that response JSON matches a given JSON string. + * + * @param array|string $json + * @param mixed $response + */ + public function assertResponseJsonEquals($json, $response) + { + Assert::assertEquals(is_array($json) ? json_encode($json) : $json, $response->getContent()); + } +} diff --git a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php new file mode 100644 index 000000000..fb8f70086 --- /dev/null +++ b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php @@ -0,0 +1,39 @@ +request->attributes->has($attribute), 'Assert that request mock has ' . $attribute . ' attribute.'); + } + + /** + * Assert a request does not have an attribute assigned to it. + * + * @param string $attribute + */ + public function assertRequestMissingAttribute(string $attribute) + { + 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 + * @param string $attribute + */ + public function assertRequestAttributeEquals($expected, string $attribute) + { + Assert::assertEquals($expected, $this->request->attributes->get($attribute)); + } +} diff --git a/tests/Assertions/NestedObjectAssertionsTrait.php b/tests/Assertions/NestedObjectAssertionsTrait.php new file mode 100644 index 000000000..b402696d8 --- /dev/null +++ b/tests/Assertions/NestedObjectAssertionsTrait.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Assertions; + +use PHPUnit\Framework\Assert; +use PHPUnit_Util_InvalidArgumentHelper; + +trait NestedObjectAssertionsTrait +{ + /** + * Assert that an object value matches an expected value. + * + * @param string $key + * @param mixed $expected + * @param object $object + */ + public function assertObjectNestedValueEquals(string $key, $expected, $object) + { + if (! is_object($object)) { + throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'object'); + } + + Assert::assertEquals($expected, object_get_strict($object, $key, '__TEST_FAILURE'), 'Assert that an object value equals a provided value.'); + } + + /** + * Assert that an object contains a nested key. + * + * @param string $key + * @param object $object + */ + public function assertObjectHasNestedAttribute(string $key, $object) + { + if (! is_object($object)) { + throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object'); + } + + Assert::assertNotEquals('__TEST_FAILURE', object_get_strict($object, $key, '__TEST_FAILURE'), 'Assert that an object contains a nested key.'); + } +} diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php new file mode 100644 index 000000000..ab9240255 --- /dev/null +++ b/tests/CreatesApplication.php @@ -0,0 +1,22 @@ +make(Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 2c3a83c83..000000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,16 +0,0 @@ -visit('/') - ->see('Laravel 5'); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 916b8ad13..427744f71 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,25 +1,40 @@ make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + $this->setKnownUuidFactory(); + } - return $app; + /** + * Tear down tests. + */ + protected function tearDown() + { + parent::tearDown(); + + Chronos::setTestNow(); + } + + /** + * Handles the known UUID handling in certain unit tests. Use the "KnownUuid" trait + * in order to enable this ability. + */ + public function setKnownUuidFactory() + { + // do nothing } } diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php new file mode 100644 index 000000000..5c34c4fb6 --- /dev/null +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -0,0 +1,26 @@ +request)) { + throw new BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); + } + + return function ($response) { + $this->assertInstanceOf(Request::class, $response); + $this->assertSame($this->request, $response); + }; + } +} diff --git a/tests/Traits/Http/RequestMockHelpers.php b/tests/Traits/Http/RequestMockHelpers.php new file mode 100644 index 000000000..f33ff71e9 --- /dev/null +++ b/tests/Traits/Http/RequestMockHelpers.php @@ -0,0 +1,108 @@ +requestMockClass = $class; + + $this->buildRequestMock(); + } + + /** + * Configure the user model that the request mock should return with. + * + * @param \Pterodactyl\Models\User|null $user + */ + public function setRequestUserModel(User $user = null) + { + $this->request->shouldReceive('user')->andReturn($user); + } + + /** + * Generates a new request user model and also returns the generated model. + * + * @param array $args + * @return \Pterodactyl\Models\User + */ + public function generateRequestUserModel(array $args = []): User + { + $user = factory(User::class)->make($args); + $this->setRequestUserModel($user); + + return $user; + } + + /** + * Set a request attribute on the mock object. + * + * @param string $attribute + * @param mixed $value + */ + public function setRequestAttribute(string $attribute, $value) + { + $this->request->attributes->set($attribute, $value); + } + + /** + * Set the request route name. + * + * @param string $name + */ + public function setRequestRouteName(string $name) + { + $this->request->shouldReceive('route->getName')->andReturn($name); + } + + /** + * Set the active request object to be an instance of a mocked request. + */ + protected function buildRequestMock() + { + $this->request = m::mock($this->requestMockClass); + if (! $this->request instanceof Request) { + throw new InvalidArgumentException('Request mock class must be an instance of ' . Request::class . ' when mocked.'); + } + + $this->request->attributes = new ParameterBag(); + } + + /** + * Sets the mocked request user. If a user model is not provided, a factory model + * will be created and returned. + * + * @param \Pterodactyl\Models\User|null $user + * @return \Pterodactyl\Models\User + * @deprecated + */ + protected function setRequestUser(User $user = null): User + { + $user = $user instanceof User ? $user : factory(User::class)->make(); + $this->request->shouldReceive('user')->withNoArgs()->andReturn($user); + + return $user; + } +} diff --git a/tests/Traits/MocksRequestException.php b/tests/Traits/MocksRequestException.php new file mode 100644 index 000000000..974fcf0e9 --- /dev/null +++ b/tests/Traits/MocksRequestException.php @@ -0,0 +1,43 @@ +getExceptionMock($abstract)->shouldReceive('getResponse')->andReturn(value($response)); + } + + /** + * Return a mocked instance of the request exception. + * + * @param string $abstract + * @return \Mockery\MockInterface + */ + protected function getExceptionMock(string $abstract = RequestException::class): MockInterface + { + return $this->exception ?? $this->exception = Mockery::mock($abstract); + } +} diff --git a/tests/Traits/MocksUuids.php b/tests/Traits/MocksUuids.php new file mode 100644 index 000000000..af57f93a8 --- /dev/null +++ b/tests/Traits/MocksUuids.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Traits; + +use Mockery as m; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidFactory; + +trait MocksUuids +{ + /** + * The known UUID string. + * + * @var string + */ + protected $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; + + /** + * Setup a factory mock to produce the same UUID whenever called. + */ + public function setKnownUuidFactory() + { + $uuid = Uuid::fromString($this->getKnownUuid()); + $factoryMock = m::mock(UuidFactory::class . '[uuid4]', [ + 'uuid4' => $uuid, + ]); + + Uuid::setFactory($factoryMock); + } + + /** + * Returns the known UUID for tests to use. + * + * @return string + */ + public function getKnownUuid(): string + { + return $this->knownUuid; + } +} diff --git a/tests/Unit/Commands/CommandTestCase.php b/tests/Unit/Commands/CommandTestCase.php new file mode 100644 index 000000000..7786a4dcf --- /dev/null +++ b/tests/Unit/Commands/CommandTestCase.php @@ -0,0 +1,59 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands; + +use Tests\TestCase; +use Illuminate\Console\Command; +use Illuminate\Contracts\Foundation\Application; +use Symfony\Component\Console\Tester\CommandTester; + +abstract class CommandTestCase extends TestCase +{ + /** + * @var bool + */ + protected $commandIsInteractive = true; + + /** + * Set a command to be non-interactive for testing purposes. + * + * @return $this + */ + public function withoutInteraction() + { + $this->commandIsInteractive = false; + + return $this; + } + + /** + * Return the display from running a command. + * + * @param \Illuminate\Console\Command $command + * @param array $args + * @param array $inputs + * @param array $opts + * @return string + */ + protected function runCommand(Command $command, array $args = [], array $inputs = [], array $opts = []) + { + if (! $command->getLaravel() instanceof Application) { + $command->setLaravel($this->app); + } + + $response = new CommandTester($command); + $response->setInputs($inputs); + + $opts = array_merge($opts, ['interactive' => $this->commandIsInteractive]); + $response->execute($args, $opts); + + return $response->getDisplay(); + } +} diff --git a/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php b/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php new file mode 100644 index 000000000..3b6865c45 --- /dev/null +++ b/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php @@ -0,0 +1,280 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\Environment; + +use Mockery as m; +use Tests\Unit\Commands\CommandTestCase; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Console\Commands\Environment\EmailSettingsCommand; + +class EmailSettingsCommandTest extends CommandTestCase +{ + /** + * @var \Pterodactyl\Console\Commands\Environment\EmailSettingsCommand|\Mockery\Mock + */ + protected $command; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->command = m::mock(EmailSettingsCommand::class . '[call, writeToEnvironment]', [$this->config]); + $this->command->setLaravel($this->app); + } + + /** + * Test selection of the SMTP driver with no options passed. + */ + public function testSmtpDriverSelection() + { + $data = [ + 'MAIL_DRIVER' => 'smtp', + 'MAIL_HOST' => 'mail.test.com', + 'MAIL_PORT' => '567', + 'MAIL_USERNAME' => 'username', + 'MAIL_PASSWORD' => 'password', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->runCommand($this->command, [], array_values($data)); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test that the command can run when all variables are passed in as options. + */ + public function testSmtpDriverSelectionWithOptionsPassed() + { + $data = [ + 'MAIL_DRIVER' => 'smtp', + 'MAIL_HOST' => 'mail.test.com', + 'MAIL_PORT' => '567', + 'MAIL_USERNAME' => 'username', + 'MAIL_PASSWORD' => 'password', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--driver' => $data['MAIL_DRIVER'], + '--email' => $data['MAIL_FROM'], + '--from' => $data['MAIL_FROM_NAME'], + '--encryption' => $data['MAIL_ENCRYPTION'], + '--host' => $data['MAIL_HOST'], + '--port' => $data['MAIL_PORT'], + '--username' => $data['MAIL_USERNAME'], + '--password' => $data['MAIL_PASSWORD'], + ]); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test selection of PHP mail() as the driver. + */ + public function testPHPMailDriverSelection() + { + $data = [ + 'MAIL_DRIVER' => 'mail', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + + // The driver flag is passed because there seems to be some issue with the command tester + // when using a choice() method when two keys start with the same letters. + // + // In this case, mail and mailgun. + unset($data['MAIL_DRIVER']); + $display = $this->runCommand($this->command, ['--driver' => 'mail'], array_values($data)); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test selection of the Mailgun driver with no options passed. + */ + public function testMailgunDriverSelection() + { + $data = [ + 'MAIL_DRIVER' => 'mailgun', + 'MAILGUN_DOMAIN' => 'domain.com', + 'MAILGUN_SECRET' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->runCommand($this->command, [], array_values($data)); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test mailgun driver selection when variables are passed as options. + */ + public function testMailgunDriverSelectionWithOptionsPassed() + { + $data = [ + 'MAIL_DRIVER' => 'mailgun', + 'MAILGUN_DOMAIN' => 'domain.com', + 'MAILGUN_SECRET' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--driver' => $data['MAIL_DRIVER'], + '--email' => $data['MAIL_FROM'], + '--from' => $data['MAIL_FROM_NAME'], + '--encryption' => $data['MAIL_ENCRYPTION'], + '--host' => $data['MAILGUN_DOMAIN'], + '--password' => $data['MAILGUN_SECRET'], + ]); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test selection of the Mandrill driver with no options passed. + */ + public function testMandrillDriverSelection() + { + $data = [ + 'MAIL_DRIVER' => 'mandrill', + 'MANDRILL_SECRET' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->runCommand($this->command, [], array_values($data)); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test mandrill driver selection when variables are passed as options. + */ + public function testMandrillDriverSelectionWithOptionsPassed() + { + $data = [ + 'MAIL_DRIVER' => 'mandrill', + 'MANDRILL_SECRET' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--driver' => $data['MAIL_DRIVER'], + '--email' => $data['MAIL_FROM'], + '--from' => $data['MAIL_FROM_NAME'], + '--encryption' => $data['MAIL_ENCRYPTION'], + '--password' => $data['MANDRILL_SECRET'], + ]); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test selection of the Postmark driver with no options passed. + */ + public function testPostmarkDriverSelection() + { + $data = [ + 'MAIL_DRIVER' => 'smtp', + 'MAIL_HOST' => 'smtp.postmarkapp.com', + 'MAIL_PORT' => '587', + 'MAIL_USERNAME' => '123456', + 'MAIL_PASSWORD' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->runCommand($this->command, [], [ + 'postmark', '123456', $data['MAIL_FROM'], $data['MAIL_FROM_NAME'], $data['MAIL_ENCRYPTION'], + ]); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Test postmark driver selection when variables are passed as options. + */ + public function testPostmarkDriverSelectionWithOptionsPassed() + { + $data = [ + 'MAIL_DRIVER' => 'smtp', + 'MAIL_HOST' => 'smtp.postmarkapp.com', + 'MAIL_PORT' => '587', + 'MAIL_USERNAME' => '123456', + 'MAIL_PASSWORD' => '123456', + 'MAIL_FROM' => 'mail@from.com', + 'MAIL_FROM_NAME' => 'MailName', + 'MAIL_ENCRYPTION' => 'tls', + ]; + + $this->setupCoreFunctions($data); + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--driver' => 'postmark', + '--email' => $data['MAIL_FROM'], + '--from' => $data['MAIL_FROM_NAME'], + '--encryption' => $data['MAIL_ENCRYPTION'], + '--username' => $data['MAIL_USERNAME'], + ]); + + $this->assertNotEmpty($display); + $this->assertContains('Updating stored environment configuration file.', $display); + } + + /** + * Setup the core functions that are repeated across all of these tests. + * + * @param array $data + */ + private function setupCoreFunctions(array $data) + { + $this->config->shouldReceive('get')->withAnyArgs()->zeroOrMoreTimes()->andReturnNull(); + $this->command->shouldReceive('writeToEnvironment')->with($data)->once()->andReturnNull(); + } +} diff --git a/tests/Unit/Commands/Location/DeleteLocationCommandTest.php b/tests/Unit/Commands/Location/DeleteLocationCommandTest.php new file mode 100644 index 000000000..43c341057 --- /dev/null +++ b/tests/Unit/Commands/Location/DeleteLocationCommandTest.php @@ -0,0 +1,128 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\Location; + +use Mockery as m; +use Pterodactyl\Models\Location; +use Tests\Unit\Commands\CommandTestCase; +use Pterodactyl\Services\Locations\LocationDeletionService; +use Pterodactyl\Console\Commands\Location\DeleteLocationCommand; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class DeleteLocationCommandTest extends CommandTestCase +{ + /** + * @var \Pterodactyl\Console\Commands\Location\DeleteLocationCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Services\Locations\LocationDeletionService|\Mockery\Mock + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->deletionService = m::mock(LocationDeletionService::class); + $this->repository = m::mock(LocationRepositoryInterface::class); + + $this->command = new DeleteLocationCommand($this->deletionService, $this->repository); + $this->command->setLaravel($this->app); + } + + /** + * Test that a location can be deleted. + */ + public function testLocationIsDeleted() + { + $locations = collect([ + $location1 = factory(Location::class)->make(), + $location2 = factory(Location::class)->make(), + ]); + + $this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations); + $this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], [$location2->short]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.deleted'), $display); + } + + /** + * Test that a location is deleted if passed in as an option. + */ + public function testLocationIsDeletedIfPassedInOption() + { + $locations = collect([ + $location1 = factory(Location::class)->make(), + $location2 = factory(Location::class)->make(), + ]); + + $this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations); + $this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull(); + + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--short' => $location2->short, + ]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.deleted'), $display); + } + + /** + * Test that prompt shows back up if the user enters the wrong parameters. + */ + public function testInteractiveEnvironmentAllowsReAttemptingSearch() + { + $locations = collect([ + $location1 = factory(Location::class)->make(), + $location2 = factory(Location::class)->make(), + ]); + + $this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations); + $this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], ['123_not_exist', 'another_not_exist', $location2->short]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.no_location_found'), $display); + $this->assertContains(trans('command/messages.location.deleted'), $display); + } + + /** + * Test that no re-attempt is performed in a non-interactive environment. + */ + public function testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound() + { + $locations = collect([ + $location1 = factory(Location::class)->make(), + $location2 = factory(Location::class)->make(), + ]); + + $this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations); + $this->deletionService->shouldNotReceive('handle'); + + $display = $this->withoutInteraction()->runCommand($this->command, ['--short' => 'randomTestString']); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.no_location_found'), $display); + } +} diff --git a/tests/Unit/Commands/Location/MakeLocationCommandTest.php b/tests/Unit/Commands/Location/MakeLocationCommandTest.php new file mode 100644 index 000000000..4490da8ac --- /dev/null +++ b/tests/Unit/Commands/Location/MakeLocationCommandTest.php @@ -0,0 +1,87 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\Location; + +use Mockery as m; +use Pterodactyl\Models\Location; +use Tests\Unit\Commands\CommandTestCase; +use Pterodactyl\Services\Locations\LocationCreationService; +use Pterodactyl\Console\Commands\Location\MakeLocationCommand; + +class MakeLocationCommandTest extends CommandTestCase +{ + /** + * @var \Pterodactyl\Console\Commands\Location\MakeLocationCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Services\Locations\LocationCreationService|\Mockery\Mock + */ + protected $creationService; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->creationService = m::mock(LocationCreationService::class); + + $this->command = new MakeLocationCommand($this->creationService); + $this->command->setLaravel($this->app); + } + + /** + * Test that a location can be created when no options are passed. + */ + public function testLocationIsCreatedWithNoOptionsPassed() + { + $location = factory(Location::class)->make(); + + $this->creationService->shouldReceive('handle')->with([ + 'short' => $location->short, + 'long' => $location->long, + ])->once()->andReturn($location); + + $display = $this->runCommand($this->command, [], [$location->short, $location->long]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.created', [ + 'name' => $location->short, + 'id' => $location->id, + ]), $display); + } + + /** + * Test that a location is created when options are passed. + */ + public function testLocationIsCreatedWhenOptionsArePassed() + { + $location = factory(Location::class)->make(); + + $this->creationService->shouldReceive('handle')->with([ + 'short' => $location->short, + 'long' => $location->long, + ])->once()->andReturn($location); + + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--short' => $location->short, + '--long' => $location->long, + ]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.location.created', [ + 'name' => $location->short, + 'id' => $location->id, + ]), $display); + } +} diff --git a/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php new file mode 100644 index 000000000..f8de38031 --- /dev/null +++ b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php @@ -0,0 +1,87 @@ +disk = m::mock(Filesystem::class); + $this->filesystem = m::mock(Factory::class); + $this->filesystem->shouldReceive('disk')->withNoArgs()->once()->andReturn($this->disk); + } + + /** + * Test that a file is deleted if it is > 5min old. + */ + public function testCommandCleansFilesMoreThan5MinutesOld() + { + $file = new SplFileInfo('testfile.txt'); + + $this->disk->shouldReceive('files')->with('services/.bak')->once()->andReturn([$file]); + $this->disk->shouldReceive('lastModified')->with($file->getPath())->once()->andReturn(Carbon::now()->subDays(100)->getTimestamp()); + $this->disk->shouldReceive('delete')->with($file->getPath())->once()->andReturnNull(); + + $display = $this->runCommand($this->getCommand()); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.maintenance.deleting_service_backup', ['file' => 'testfile.txt']), $display); + } + + /** + * Test that a file isn't deleted if it is < 5min old. + */ + public function testCommandDoesNotCleanFileLessThan5MinutesOld() + { + $file = new SplFileInfo('testfile.txt'); + + $this->disk->shouldReceive('files')->with('services/.bak')->once()->andReturn([$file]); + $this->disk->shouldReceive('lastModified')->with($file->getPath())->once()->andReturn(Carbon::now()->getTimestamp()); + + $display = $this->runCommand($this->getCommand()); + + $this->assertEmpty($display); + } + + /** + * Return an instance of the command for testing. + * + * @return \Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand + */ + private function getCommand(): CleanServiceBackupFilesCommand + { + $command = new CleanServiceBackupFilesCommand($this->filesystem); + $command->setLaravel($this->app); + + return $command; + } +} diff --git a/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php b/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php new file mode 100644 index 000000000..24f53e4de --- /dev/null +++ b/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php @@ -0,0 +1,119 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\Schedule; + +use Mockery as m; +use Carbon\Carbon; +use Pterodactyl\Models\Task; +use Pterodactyl\Models\Schedule; +use Tests\Unit\Commands\CommandTestCase; +use Pterodactyl\Services\Schedules\ProcessScheduleService; +use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand; +use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; + +class ProcessRunnableCommandTest extends CommandTestCase +{ + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Services\Schedules\ProcessScheduleService + */ + protected $processScheduleService; + + /** + * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->carbon = m::mock(Carbon::class); + $this->processScheduleService = m::mock(ProcessScheduleService::class); + $this->repository = m::mock(ScheduleRepositoryInterface::class); + + $this->command = new ProcessRunnableCommand($this->carbon, $this->processScheduleService, $this->repository); + } + + /** + * Test that a schedule can be queued up correctly. + */ + public function testScheduleIsQueued() + { + $schedule = factory(Schedule::class)->make(); + $schedule->tasks = collect([factory(Task::class)->make()]); + + $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('toAtomString')->withNoArgs()->once()->andReturn('00:00:00'); + $this->repository->shouldReceive('getSchedulesToProcess')->with('00:00:00')->once()->andReturn(collect([$schedule])); + $this->processScheduleService->shouldReceive('handle')->with($schedule)->once()->andReturnNull(); + + $display = $this->runCommand($this->command); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.schedule.output_line', [ + 'schedule' => $schedule->name, + 'hash' => $schedule->hashid, + ]), $display); + } + + /** + * If tasks is an empty collection, don't process it. + */ + public function testScheduleWithNoTasksIsNotProcessed() + { + $schedule = factory(Schedule::class)->make(); + $schedule->tasks = collect([]); + + $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('toAtomString')->withNoArgs()->once()->andReturn('00:00:00'); + $this->repository->shouldReceive('getSchedulesToProcess')->with('00:00:00')->once()->andReturn(collect([$schedule])); + + $display = $this->runCommand($this->command); + + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.schedule.output_line', [ + 'schedule' => $schedule->name, + 'hash' => $schedule->hashid, + ]), $display); + } + + /** + * If tasks isn't an instance of a collection, don't process it. + */ + public function testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed() + { + $schedule = factory(Schedule::class)->make(['tasks' => null]); + + $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('toAtomString')->withNoArgs()->once()->andReturn('00:00:00'); + $this->repository->shouldReceive('getSchedulesToProcess')->with('00:00:00')->once()->andReturn(collect([$schedule])); + + $display = $this->runCommand($this->command); + + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.schedule.output_line', [ + 'schedule' => $schedule->name, + 'hash' => $schedule->hashid, + ]), $display); + } +} diff --git a/tests/Unit/Commands/User/DeleteUserCommandTest.php b/tests/Unit/Commands/User/DeleteUserCommandTest.php new file mode 100644 index 000000000..fa5b5199d --- /dev/null +++ b/tests/Unit/Commands/User/DeleteUserCommandTest.php @@ -0,0 +1,188 @@ +deletionService = m::mock(UserDeletionService::class); + $this->repository = m::mock(UserRepositoryInterface::class); + + $this->command = new DeleteUserCommand($this->deletionService, $this->repository); + $this->command->setLaravel($this->app); + } + + /** + * Test that a user can be deleted using a normal pathway. + */ + public function testCommandWithNoOptions() + { + $users = collect([ + $user1 = factory(User::class)->make(), + $user2 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], [$user1->username, $user1->id, 'yes']); + + $this->assertNotEmpty($display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test a bad first user search followed by a good second search. + */ + public function testCommandWithInvalidInitialSearch() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with('noResults')->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn(collect()); + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], ['noResults', $user1->username, $user1->id, 'yes']); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.no_users_found'), $display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test the ability to re-do a search for a user account. + */ + public function testReSearchAbility() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->twice()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->twice()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], [$user1->username, 0, $user1->username, $user1->id, 'yes']); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.select_search_user'), $display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test that answering no works as expected when confirming deletion of account. + */ + public function testAnsweringNoToDeletionConfirmationWillNotDeleteUser() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldNotReceive('handle'); + + $display = $this->runCommand($this->command, [], [$user1->username, $user1->id, 'no']); + + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test a single result is deleted if there is no interaction setup. + */ + public function testNoInteractionWithSingleResult() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1)->once()->andReturnNull(); + + $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test that an error is returned if there is no interaction but multiple results. + */ + public function testNoInteractionWithMultipleResults() + { + $users = collect([ + $user1 = factory(User::class)->make(), + $user2 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldNotReceive('handle'); + + $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.multiple_found'), $display); + } + + /** + * Test that an error is returned if there is no interaction and no results returned. + */ + public function testNoInteractionWithNoResults() + { + $this->repository->shouldReceive('setSearchTerm')->with(123456)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn(collect()); + + $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => 123456]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.no_users_found'), $display); + } +} diff --git a/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php new file mode 100644 index 000000000..32f9e43eb --- /dev/null +++ b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\User; + +use Mockery as m; +use Pterodactyl\Models\User; +use Tests\Unit\Commands\CommandTestCase; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand; + +class DisableTwoFactorCommandTest extends CommandTestCase +{ + /** + * @var \Pterodactyl\Console\Commands\User\DisableTwoFactorCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(UserRepositoryInterface::class); + + $this->command = new DisableTwoFactorCommand($this->repository); + $this->command->setLaravel($this->app); + } + + /** + * Test 2-factor auth is disabled when no option is passed. + */ + public function testTwoFactorIsDisabledWhenNoOptionIsPassed() + { + $user = factory(User::class)->make(); + + $this->repository->shouldReceive('setColumns')->with(['id', 'email'])->once()->andReturnSelf() + ->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ])->once()->andReturnNull(); + + $display = $this->runCommand($this->command, [], [$user->email]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display); + } + + /** + * Test 2-factor auth is disabled when user is passed in option. + */ + public function testTwoFactorIsDisabledWhenOptionIsPassed() + { + $user = factory(User::class)->make(); + + $this->repository->shouldReceive('setColumns')->with(['id', 'email'])->once()->andReturnSelf() + ->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ])->once()->andReturnNull(); + + $display = $this->withoutInteraction()->runCommand($this->command, ['--email' => $user->email]); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display); + } +} diff --git a/tests/Unit/Commands/User/MakeUserCommandTest.php b/tests/Unit/Commands/User/MakeUserCommandTest.php new file mode 100644 index 000000000..2fc9b9b4b --- /dev/null +++ b/tests/Unit/Commands/User/MakeUserCommandTest.php @@ -0,0 +1,129 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Commands\User; + +use Mockery as m; +use Pterodactyl\Models\User; +use Tests\Unit\Commands\CommandTestCase; +use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Console\Commands\User\MakeUserCommand; + +class MakeUserCommandTest extends CommandTestCase +{ + /** + * @var \Pterodactyl\Console\Commands\User\MakeUserCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $creationService; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->creationService = m::mock(UserCreationService::class); + + $this->command = new MakeUserCommand($this->creationService); + $this->command->setLaravel($this->app); + } + + /** + * Test that the command executes if no options are passed. + */ + public function testCommandWithNoPassedOptions() + { + $user = factory(User::class)->make(['root_admin' => true]); + + $this->creationService->shouldReceive('handle')->with([ + 'email' => $user->email, + 'username' => $user->username, + 'name_first' => $user->name_first, + 'name_last' => $user->name_last, + 'password' => 'Password123', + 'root_admin' => $user->root_admin, + ])->once()->andReturn($user); + + $display = $this->runCommand($this->command, [], [ + 'yes', $user->email, $user->username, $user->name_first, $user->name_last, 'Password123', + ]); + + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.ask_password_help'), $display); + $this->assertContains($user->uuid, $display); + $this->assertContains($user->email, $display); + $this->assertContains($user->username, $display); + $this->assertContains($user->name, $display); + $this->assertContains('Yes', $display); + } + + /** + * Test that the --no-password flag works as intended. + */ + public function testCommandWithNoPasswordOption() + { + $user = factory(User::class)->make(['root_admin' => true]); + + $this->creationService->shouldReceive('handle')->with([ + 'email' => $user->email, + 'username' => $user->username, + 'name_first' => $user->name_first, + 'name_last' => $user->name_last, + 'password' => null, + 'root_admin' => $user->root_admin, + ])->once()->andReturn($user); + + $display = $this->runCommand($this->command, ['--no-password' => true], [ + 'yes', $user->email, $user->username, $user->name_first, $user->name_last, + ]); + + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.user.ask_password_help'), $display); + } + + /** + * Test command when arguments are passed as flags. + */ + public function testCommandWithOptionsPassed() + { + $user = factory(User::class)->make(['root_admin' => false]); + + $this->creationService->shouldReceive('handle')->with([ + 'email' => $user->email, + 'username' => $user->username, + 'name_first' => $user->name_first, + 'name_last' => $user->name_last, + 'password' => 'Password123', + 'root_admin' => $user->root_admin, + ])->once()->andReturn($user); + + $display = $this->withoutInteraction()->runCommand($this->command, [ + '--email' => $user->email, + '--username' => $user->username, + '--name-first' => $user->name_first, + '--name-last' => $user->name_last, + '--password' => 'Password123', + '--admin' => 0, + ]); + + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.user.ask_password_help'), $display); + $this->assertContains($user->uuid, $display); + $this->assertContains($user->email, $display); + $this->assertContains($user->username, $display); + $this->assertContains($user->name, $display); + $this->assertContains('No', $display); + } +} diff --git a/tests/Unit/Helpers/HumanReadableTest.php b/tests/Unit/Helpers/HumanReadableTest.php new file mode 100644 index 000000000..92af8f700 --- /dev/null +++ b/tests/Unit/Helpers/HumanReadableTest.php @@ -0,0 +1,48 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Helpers; + +use Tests\TestCase; + +class HumanReadableTest extends TestCase +{ + /** + * Test the human_readable helper. + * + * @dataProvider helperDataProvider + */ + public function testHelper($value, $response, $precision = 2) + { + $this->assertSame($response, human_readable($value, $precision)); + } + + /** + * Provide data to test aganist the helper function. + * + * @return array + */ + public function helperDataProvider() + { + return [ + [0, '0B'], + [1, '1B'], + [1024, '1kB'], + [10392, '10.15kB'], + [10392, '10kB', 0], + [10392, '10.148438kB', 6], + [1024000, '0.98MB'], + [1024000, '1MB', 0], + [102400000, '97.66MB'], + [102400000, '98MB', 0], + [102400000, '97.6563MB', 4], + [102400000, '97.65625MB', 10], + ]; + } +} diff --git a/tests/Unit/Helpers/IsDigitTest.php b/tests/Unit/Helpers/IsDigitTest.php new file mode 100644 index 000000000..bf445688c --- /dev/null +++ b/tests/Unit/Helpers/IsDigitTest.php @@ -0,0 +1,57 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Helpers; + +use Tests\TestCase; + +class IsDigitTest extends TestCase +{ + /** + * Test the is_digit helper. + * + * @dataProvider helperDataProvider + */ + public function testHelper($value, $response) + { + $this->assertSame($response, is_digit($value)); + } + + /** + * Provide data to test aganist the helper function. + * + * @return array + */ + public function helperDataProvider() + { + return [ + [true, false], + [false, false], + [12.3, false], + ['12.3', false], + ['string', false], + [-1, false], + ['-1', false], + [1, true], + [0, true], + [12345, true], + ['12345', true], + ['true', false], + ['false', false], + ['123_test', false], + ['123.test', false], + ['123test', false], + ['test123', false], + ['0x00000003', false], + [00000011, true], + ['00000011', true], + ['AD9C', false], + ]; + } +} diff --git a/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php b/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php new file mode 100644 index 000000000..108198bde --- /dev/null +++ b/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php @@ -0,0 +1,127 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Admin; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\DatabaseHost; +use Prologue\Alerts\AlertsMessageBag; +use Tests\Assertions\ControllerAssertionsTrait; +use Pterodactyl\Http\Controllers\Admin\DatabaseController; +use Pterodactyl\Services\Databases\Hosts\HostUpdateService; +use Pterodactyl\Services\Databases\Hosts\HostCreationService; +use Pterodactyl\Services\Databases\Hosts\HostDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class DatabaseControllerTest extends TestCase +{ + use ControllerAssertionsTrait; + + /** + * @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock + */ + private $alert; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService|\Mockery\Mock + */ + private $creationService; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService|\Mockery\Mock + */ + private $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface|\Mockery\Mock + */ + private $locationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService|\Mockery\Mock + */ + private $updateService; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->alert = m::mock(AlertsMessageBag::class); + $this->creationService = m::mock(HostCreationService::class); + $this->deletionService = m::mock(HostDeletionService::class); + $this->locationRepository = m::mock(LocationRepositoryInterface::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + $this->updateService = m::mock(HostUpdateService::class); + } + + /** + * Test the index controller. + */ + public function testIndexController() + { + $this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes'])); + $this->repository->shouldReceive('getWithViewDetails')->withNoArgs()->once()->andReturn(collect(['getWithViewDetails'])); + + $response = $this->getController()->index(); + + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('admin.databases.index', $response); + $this->assertViewHasKey('locations', $response); + $this->assertViewHasKey('hosts', $response); + $this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response); + $this->assertViewKeyEquals('hosts', collect(['getWithViewDetails']), $response); + } + + /** + * Test the view controller for displaying a specific database host. + */ + public function testViewController() + { + $model = factory(DatabaseHost::class)->make(); + + $this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes'])); + $this->repository->shouldReceive('getWithServers')->with(1)->once()->andReturn($model); + + $response = $this->getController()->view(1); + + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('admin.databases.view', $response); + $this->assertViewHasKey('locations', $response); + $this->assertViewHasKey('host', $response); + $this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response); + $this->assertViewKeyEquals('host', $model, $response); + } + + /** + * Return an instance of the DatabaseController with mock dependencies. + * + * @return \Pterodactyl\Http\Controllers\Admin\DatabaseController + */ + private function getController(): DatabaseController + { + return new DatabaseController( + $this->alert, + $this->repository, + $this->creationService, + $this->deletionService, + $this->updateService, + $this->locationRepository + ); + } +} diff --git a/tests/Unit/Http/Controllers/Base/AccountControllerTest.php b/tests/Unit/Http/Controllers/Base/AccountControllerTest.php new file mode 100644 index 000000000..968019ad7 --- /dev/null +++ b/tests/Unit/Http/Controllers/Base/AccountControllerTest.php @@ -0,0 +1,118 @@ +alert = m::mock(AlertsMessageBag::class); + $this->updateService = m::mock(UserUpdateService::class); + } + + /** + * Test the index controller. + */ + public function testIndexController() + { + $response = $this->getController()->index(); + + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('base.account', $response); + } + + /** + * Test controller when password is being updated. + */ + public function testUpdateControllerForPassword() + { + $this->setRequestMockClass(AccountDataFormRequest::class); + $user = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('do_action')->andReturn('password'); + $this->request->shouldReceive('input')->with('new_password')->once()->andReturn('test-password'); + + $this->updateService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull(); + $this->updateService->shouldReceive('handle')->with($user, ['password' => 'test-password'])->once()->andReturn(collect()); + $this->alert->shouldReceive('success->flash')->once()->andReturnNull(); + + $response = $this->getController()->update($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account', $response); + } + + /** + * Test controller when email is being updated. + */ + public function testUpdateControllerForEmail() + { + $this->setRequestMockClass(AccountDataFormRequest::class); + $user = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('do_action')->andReturn('email'); + $this->request->shouldReceive('input')->with('new_email')->once()->andReturn('test@example.com'); + + $this->updateService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull(); + $this->updateService->shouldReceive('handle')->with($user, ['email' => 'test@example.com'])->once()->andReturn(collect()); + $this->alert->shouldReceive('success->flash')->once()->andReturnNull(); + + $response = $this->getController()->update($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account', $response); + } + + /** + * Test controller when identity is being updated. + */ + public function testUpdateControllerForIdentity() + { + $this->setRequestMockClass(AccountDataFormRequest::class); + $user = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('do_action')->andReturn('identity'); + $this->request->shouldReceive('only')->with(['name_first', 'name_last', 'username'])->once()->andReturn([ + 'test_data' => 'value', + ]); + + $this->updateService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull(); + $this->updateService->shouldReceive('handle')->with($user, ['test_data' => 'value'])->once()->andReturn(collect()); + $this->alert->shouldReceive('success->flash')->once()->andReturnNull(); + + $response = $this->getController()->update($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account', $response); + } + + /** + * Return an instance of the controller for testing. + * + * @return \Pterodactyl\Http\Controllers\Base\AccountController + */ + private function getController(): AccountController + { + return new AccountController($this->alert, $this->updateService); + } +} diff --git a/tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php b/tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php new file mode 100644 index 000000000..11cbaff10 --- /dev/null +++ b/tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php @@ -0,0 +1,123 @@ +markTestSkipped('Not implemented'); + + $this->alert = m::mock(AlertsMessageBag::class); + $this->keyService = m::mock(KeyCreationService::class); + $this->repository = m::mock(ApiKeyRepositoryInterface::class); + } + + /** + * Test the index controller. + */ + public function testIndexController() + { + $model = $this->generateRequestUserModel(); + + $this->repository->shouldReceive('getAccountKeys')->with($model)->once()->andReturn(collect(['testkeys'])); + + $response = $this->getController()->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('base.api.index', $response); + $this->assertViewHasKey('keys', $response); + $this->assertViewKeyEquals('keys', collect(['testkeys']), $response); + } + + /** + * Test the create API view controller. + */ + public function testCreateController() + { + $this->generateRequestUserModel(); + + $response = $this->getController()->create($this->request); + $this->assertIsViewResponse($response); + } + + /** + * Test the store functionality for a user. + */ + public function testStoreController() + { + $this->setRequestMockClass(StoreAccountKeyRequest::class); + $model = $this->generateRequestUserModel(); + $keyModel = factory(ApiKey::class)->make(); + + $this->request->shouldReceive('user')->withNoArgs()->andReturn($model); + $this->request->shouldReceive('input')->with('allowed_ips')->once()->andReturnNull(); + $this->request->shouldReceive('input')->with('memo')->once()->andReturnNull(); + + $this->keyService->shouldReceive('setKeyType')->with(ApiKey::TYPE_ACCOUNT)->once()->andReturnSelf(); + $this->keyService->shouldReceive('handle')->with([ + 'user_id' => $model->id, + 'allowed_ips' => null, + 'memo' => null, + ])->once()->andReturn($keyModel); + + $this->alert->shouldReceive('success')->with(trans('base.api.index.keypair_created'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getController()->store($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account.api', $response); + } + + /** + * Test the API key revocation controller. + */ + public function testRevokeController() + { + $model = $this->generateRequestUserModel(); + + $this->repository->shouldReceive('deleteAccountKey')->with($model, 'testIdentifier')->once()->andReturn(1); + + $response = $this->getController()->revoke($this->request, 'testIdentifier'); + $this->assertIsResponse($response); + $this->assertEmpty($response->getContent()); + $this->assertResponseCodeEquals(204, $response); + } + + /** + * Return an instance of the controller with mocked dependencies for testing. + * + * @return \Pterodactyl\Http\Controllers\Base\AccountKeyController + */ + private function getController(): AccountKeyController + { + return new AccountKeyController($this->alert, $this->repository, $this->keyService); + } +} diff --git a/tests/Unit/Http/Controllers/Base/IndexControllerTest.php b/tests/Unit/Http/Controllers/Base/IndexControllerTest.php new file mode 100644 index 000000000..6056747ed --- /dev/null +++ b/tests/Unit/Http/Controllers/Base/IndexControllerTest.php @@ -0,0 +1,137 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Base; + +use Mockery as m; +use Pterodactyl\Models\User; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use Tests\Assertions\ControllerAssertionsTrait; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Http\Controllers\Base\IndexController; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class IndexControllerTest extends ControllerTestCase +{ + use ControllerAssertionsTrait; + + /** + * @var \Pterodactyl\Http\Controllers\Base\IndexController + */ + protected $controller; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock + */ + protected $keyProviderService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + + $this->controller = new IndexController($this->keyProviderService, $this->daemonRepository, $this->repository); + } + + /** + * Test the index controller. + */ + public function testIndexController() + { + $paginator = m::mock(LengthAwarePaginator::class); + $model = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('query')->once()->andReturn('searchTerm'); + $this->repository->shouldReceive('setSearchTerm')->with('searchTerm')->once()->andReturnSelf() + ->shouldReceive('filterUserAccessServers')->with($model, User::FILTER_LEVEL_ALL) + ->once()->andReturn($paginator); + + $response = $this->controller->getIndex($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('base.index', $response); + $this->assertViewHasKey('servers', $response); + $this->assertViewKeyEquals('servers', $paginator, $response); + } + + /** + * Test the status controller. + */ + public function testStatusController() + { + $user = $this->generateRequestUserModel(); + $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]); + $psrResponse = new Response; + + $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); + $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123'); + + $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('test123')->once()->andReturnSelf() + ->shouldReceive('details')->withNoArgs()->once()->andReturn($psrResponse); + + $response = $this->controller->status($this->request, $server->uuidShort); + $this->assertIsJsonResponse($response); + $this->assertResponseJsonEquals(json_encode($psrResponse->getBody()), $response); + } + + /** + * Test the status controller if a server is not installed. + */ + public function testStatusControllerWhenServerNotInstalled() + { + $user = $this->generateRequestUserModel(); + $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 0]); + + $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); + $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123'); + + $response = $this->controller->status($this->request, $server->uuidShort); + $this->assertIsJsonResponse($response); + $this->assertResponseCodeEquals(200, $response); + $this->assertResponseJsonEquals(['status' => 20], $response); + } + + /** + * Test the status controller when a server is suspended. + */ + public function testStatusControllerWhenServerIsSuspended() + { + $user = factory(User::class)->make(); + $server = factory(Server::class)->make(['suspended' => 1, 'installed' => 1]); + + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user); + $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); + $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123'); + + $response = $this->controller->status($this->request, $server->uuidShort); + $this->assertIsJsonResponse($response); + $this->assertResponseCodeEquals(200, $response); + $this->assertResponseJsonEquals(['status' => 30], $response); + } +} diff --git a/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php b/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php new file mode 100644 index 000000000..72435791b --- /dev/null +++ b/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php @@ -0,0 +1,162 @@ +alert = m::mock(AlertsMessageBag::class); + $this->config = m::mock(Repository::class); + $this->repository = m::mock(SessionRepositoryInterface::class); + $this->toggleTwoFactorService = m::mock(ToggleTwoFactorService::class); + $this->twoFactorSetupService = m::mock(TwoFactorSetupService::class); + } + + /** + * Test the index controller when using a database driver. + */ + public function testIndexControllerWithDatabaseDriver() + { + $model = $this->generateRequestUserModel(); + + $this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('database'); + $this->repository->shouldReceive('getUserSessions')->with($model->id)->once()->andReturn(collect(['sessions'])); + + $response = $this->getController()->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('base.security', $response); + $this->assertViewHasKey('sessions', $response); + $this->assertViewKeyEquals('sessions', collect(['sessions']), $response); + } + + /** + * Test the index controller when not using the database driver. + */ + public function testIndexControllerWithoutDatabaseDriver() + { + $this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('redis'); + + $response = $this->getController()->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('base.security', $response); + $this->assertViewHasKey('sessions', $response); + $this->assertViewKeyEquals('sessions', null, $response); + } + + /** + * Test TOTP generation controller. + */ + public function testGenerateTotpController() + { + $model = $this->generateRequestUserModel(); + + $this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn('qrCodeImage'); + + $response = $this->getController()->generateTotp($this->request); + $this->assertIsJsonResponse($response); + $this->assertResponseJsonEquals(['qrImage' => 'qrCodeImage'], $response); + } + + /** + * Test the disable totp controller when no exception is thrown by the service. + */ + public function testDisableTotpControllerSuccess() + { + $model = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken'); + $this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andReturn(true); + + $response = $this->getController()->disableTotp($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account.security', $response); + } + + /** + * Test the disable totp controller when an exception is thrown by the service. + */ + public function testDisableTotpControllerWhenExceptionIsThrown() + { + $model = $this->generateRequestUserModel(); + + $this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken'); + $this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andThrow(new TwoFactorAuthenticationTokenInvalid); + $this->alert->shouldReceive('danger')->with(trans('base.security.2fa_disable_error'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getController()->disableTotp($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account.security', $response); + } + + /** + * Test the revoke controller. + */ + public function testRevokeController() + { + $model = $this->generateRequestUserModel(); + + $this->repository->shouldReceive('deleteUserSession')->with($model->id, 123)->once()->andReturnNull(); + + $response = $this->getController()->revoke($this->request, 123); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('account.security', $response); + } + + /** + * Return an instance of the controller for testing with mocked dependencies. + * + * @return \Pterodactyl\Http\Controllers\Base\SecurityController + */ + private function getController(): SecurityController + { + return new SecurityController( + $this->alert, + $this->config, + $this->repository, + $this->toggleTwoFactorService, + $this->twoFactorSetupService + ); + } +} diff --git a/tests/Unit/Http/Controllers/ControllerTestCase.php b/tests/Unit/Http/Controllers/ControllerTestCase.php new file mode 100644 index 000000000..6caf9abda --- /dev/null +++ b/tests/Unit/Http/Controllers/ControllerTestCase.php @@ -0,0 +1,86 @@ +buildRequestMock(); + } + + /** + * Set an instance of the controller. + * + * @param \Pterodactyl\Http\Controllers\Controller|\Mockery\Mock $controller + */ + public function setControllerInstance($controller) + { + $this->controller = $controller; + } + + /** + * Return an instance of the controller. + * + * @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller + */ + public function getControllerInstance() + { + return $this->controller; + } + + /** + * Helper function to mock injectJavascript requests. + * + * @param array|null $args + * @param bool $subset + */ + protected function mockInjectJavascript(array $args = null, bool $subset = false) + { + $controller = $this->getControllerInstance(); + + $controller->shouldReceive('setRequest')->with($this->request)->once()->andReturnSelf(); + if (is_null($args)) { + $controller->shouldReceive('injectJavascript')->withAnyArgs()->once()->andReturnNull(); + } else { + $with = $subset ? m::subset($args) : $args; + + $controller->shouldReceive('injectJavascript')->with($with)->once()->andReturnNull(); + } + } + + /** + * Build and return a mocked controller instance to use for testing. + * + * @param string $class + * @param array $args + * @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller + */ + protected function buildMockedController(string $class, array $args = []) + { + $controller = m::mock($class, $args)->makePartial(); + + if (is_null($this->getControllerInstance())) { + $this->setControllerInstance($controller); + } + + return $this->getControllerInstance(); + } +} diff --git a/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php new file mode 100644 index 000000000..ef6334657 --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php @@ -0,0 +1,77 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Server; + +use Mockery as m; +use Pterodactyl\Models\Server; +use Illuminate\Contracts\Config\Repository; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Http\Controllers\Server\ConsoleController; + +class ConsoleControllerTest extends ControllerTestCase +{ + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + } + + /** + * Test both controllers as they do effectively the same thing. + * + * @dataProvider controllerDataProvider + */ + public function testAllControllers($function, $view) + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $this->config->shouldReceive('get')->with('pterodactyl.console.count')->once()->andReturn(100); + $this->config->shouldReceive('get')->with('pterodactyl.console.frequency')->once()->andReturn(10); + + $response = $controller->$function($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals($view, $response); + } + + /** + * Provide data for the tests. + * + * @return array + */ + public function controllerDataProvider() + { + return [ + ['index', 'server.index'], + ['console', 'server.console'], + ]; + } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\ConsoleController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(ConsoleController::class, [$this->config]); + } +} diff --git a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php new file mode 100644 index 000000000..d44481c1b --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php @@ -0,0 +1,73 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Server\Files; + +use Mockery as m; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; +use Illuminate\Cache\Repository; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Http\Controllers\Server\Files\DownloadController; + +class DownloadControllerTest extends ControllerTestCase +{ + use PHPMock; + + /** + * @var \Illuminate\Cache\Repository|\Mockery\Mock + */ + protected $cache; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->cache = m::mock(Repository::class); + } + + /** + * Test the download controller redirects correctly. + */ + public function testIndexController() + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + $server->setRelation('node', factory(Node::class)->make()); + + $this->setRequestAttribute('server', $server); + + $controller->shouldReceive('authorize')->with('download-files', $server)->once()->andReturnNull(); + $this->getFunctionMock('\\Pterodactyl\\Http\\Controllers\\Server\\Files', 'str_random') + ->expects($this->once())->willReturn('randomString'); + + $this->cache->shouldReceive('tags')->with(['Server:Downloads'])->once()->andReturnSelf(); + $this->cache->shouldReceive('put')->with('randomString', ['server' => $server->uuid, 'path' => '/my/file.txt'], 5)->once()->andReturnNull(); + + $response = $controller->index($this->request, $server->uuidShort, '/my/file.txt'); + $this->assertIsRedirectResponse($response); + $this->assertRedirectUrlEquals(sprintf( + '%s://%s:%s/v1/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, 'randomString' + ), $response); + } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\DownloadController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(DownloadController::class, [$this->cache]); + } +} diff --git a/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php new file mode 100644 index 000000000..feebea845 --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php @@ -0,0 +1,185 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Server\Files; + +use Mockery as m; +use Pterodactyl\Models\Server; +use Tests\Traits\MocksRequestException; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\PterodactylException; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Http\Controllers\Server\Files\FileActionsController; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; + +class FileActionsControllerTest extends ControllerTestCase +{ + use MocksRequestException; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(FileRepositoryInterface::class); + } + + /** + * Test the index view controller. + */ + public function testIndexController() + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('user->can')->andReturn(true); + + $response = $controller->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.index', $response); + } + + /** + * Test the file creation view controller. + * + * @dataProvider directoryNameProvider + */ + public function testCreateController($directory, $expected) + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('create-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('get')->with('dir')->andReturn($directory); + + $response = $controller->create($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.add', $response); + $this->assertViewHasKey('directory', $response); + $this->assertViewKeyEquals('directory', $expected, $response); + } + + /** + * Test the update controller. + * + * @dataProvider fileNameProvider + */ + public function testUpdateController($file, $expected) + { + $this->setRequestMockClass(UpdateFileContentsFormRequest::class); + + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + $this->setRequestAttribute('file_stats', 'fileStatsObject'); + $this->mockInjectJavascript(['stat' => 'fileStatsObject']); + + $this->repository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('abc123')->once()->andReturnSelf() + ->shouldReceive('getContent')->with($file)->once()->andReturn('test'); + + $response = $controller->view($this->request, '1234', $file); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.edit', $response); + $this->assertViewHasKey('file', $response); + $this->assertViewHasKey('stat', $response); + $this->assertViewHasKey('contents', $response); + $this->assertViewHasKey('directory', $response); + $this->assertViewKeyEquals('file', $file, $response); + $this->assertViewKeyEquals('stat', 'fileStatsObject', $response); + $this->assertViewKeyEquals('contents', 'test', $response); + $this->assertViewKeyEquals('directory', $expected, $response); + } + + /** + * Test that an exception is handled correctly in the controller. + */ + public function testExceptionRenderedByUpdateController() + { + $this->setRequestMockClass(UpdateFileContentsFormRequest::class); + $this->configureExceptionMock(); + + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + $this->setRequestAttribute('file_stats', 'fileStatsObject'); + + $this->repository->shouldReceive('setServer')->with($server)->once()->andThrow($this->getExceptionMock()); + + try { + $controller->view($this->request, '1234', 'file.txt'); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } + + /** + * Provides a list of directory names and the expected output from formatting. + * + * @return array + */ + public function directoryNameProvider() + { + return [ + [null, ''], + ['/', ''], + ['', ''], + ['my/directory', 'my/directory/'], + ['/my/directory/', 'my/directory/'], + ['/////my/directory////', 'my/directory/'], + ]; + } + + /** + * Provides a list of file names and the expected output from formatting. + * + * @return array + */ + public function fileNameProvider() + { + return [ + ['/my/file.txt', 'my/'], + ['my/file.txt', 'my/'], + ['file.txt', '/'], + ['/file.txt', '/'], + ['./file.txt', '/'], + ]; + } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\FileActionsController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(FileActionsController::class, [$this->repository]); + } +} diff --git a/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php new file mode 100644 index 000000000..6291a748a --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php @@ -0,0 +1,158 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Server\Files; + +use Mockery as m; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use Tests\Traits\MocksRequestException; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Exceptions\PterodactylException; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController; + +class RemoteRequestControllerTest extends ControllerTestCase +{ + use MocksRequestException; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->repository = m::mock(FileRepositoryInterface::class); + } + + /** + * Test the directory listing controller. + */ + public function testDirectoryController() + { + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('input')->with('directory', '/')->once()->andReturn('/'); + $this->repository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('abc123')->once()->andReturnSelf() + ->shouldReceive('getDirectory')->with('/')->once()->andReturn(['folders' => 1, 'files' => 2]); + $this->config->shouldReceive('get')->with('pterodactyl.files.editable')->once()->andReturn([]); + + $response = $controller->directory($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.list', $response); + $this->assertViewHasKey('files', $response); + $this->assertViewHasKey('folders', $response); + $this->assertViewHasKey('editableMime', $response); + $this->assertViewHasKey('directory', $response); + $this->assertViewKeyEquals('files', 2, $response); + $this->assertViewKeyEquals('folders', 1, $response); + $this->assertViewKeyEquals('editableMime', [], $response); + $this->assertViewKeyEquals('directory.first', false, $response); + $this->assertViewKeyEquals('directory.header', '', $response); + } + + /** + * Test that the controller properly handles an exception thrown by the daemon conneciton. + */ + public function testExceptionThrownByDaemonConnectionIsHandledByDisplayController() + { + $this->configureExceptionMock(); + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('input')->with('directory', '/')->once()->andReturn('/'); + $this->repository->shouldReceive('setServer')->with($server)->once()->andThrow($this->getExceptionMock()); + + try { + $controller->directory($this->request); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } + + /** + * Test the store controller. + */ + public function testStoreController() + { + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + + $controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('input')->with('file')->once()->andReturn('file.txt'); + $this->request->shouldReceive('input')->with('contents')->once()->andReturn('file contents'); + $this->repository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('abc123')->once()->andReturnSelf() + ->shouldReceive('putContent')->with('file.txt', 'file contents')->once()->andReturn(new Response); + + $response = $controller->store($this->request); + $this->assertIsResponse($response); + $this->assertResponseCodeEquals(204, $response); + } + + /** + * Test that the controller properly handles an exception thrown by the daemon conneciton. + */ + public function testExceptionThrownByDaemonConnectionIsHandledByStoreController() + { + $this->configureExceptionMock(); + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + + $controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); + $this->repository->shouldReceive('setServer')->with($server)->once()->andThrow($this->getExceptionMock()); + + try { + $controller->store($this->request); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(RemoteRequestController::class, [$this->config, $this->repository]); + } +} diff --git a/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php b/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php new file mode 100644 index 000000000..f9b550cfc --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php @@ -0,0 +1,226 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Http\Controllers\Server; + +use Mockery as m; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\Permission; +use Prologue\Alerts\AlertsMessageBag; +use Tests\Unit\Http\Controllers\ControllerTestCase; +use Pterodactyl\Services\Subusers\SubuserUpdateService; +use Pterodactyl\Services\Subusers\SubuserCreationService; +use Pterodactyl\Services\Subusers\SubuserDeletionService; +use Pterodactyl\Http\Controllers\Server\SubuserController; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest; + +class SubuserControllerTest extends ControllerTestCase +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserCreationService|\Mockery\Mock + */ + protected $subuserCreationService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserDeletionService|\Mockery\Mock + */ + protected $subuserDeletionService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserUpdateService|\Mockery\Mock + */ + protected $subuserUpdateService; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->alert = m::mock(AlertsMessageBag::class); + $this->repository = m::mock(SubuserRepositoryInterface::class); + $this->subuserCreationService = m::mock(SubuserCreationService::class); + $this->subuserDeletionService = m::mock(SubuserDeletionService::class); + $this->subuserUpdateService = m::mock(SubuserUpdateService::class); + } + + /* + * Test index controller. + */ + public function testIndexController() + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('list-subusers', $server)->once()->andReturnNull(); + $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $server->id]])->once()->andReturn(collect()); + + $response = $controller->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.users.index', $response); + $this->assertViewHasKey('subusers', $response); + } + + /** + * Test view controller. + */ + public function testViewController() + { + $controller = $this->getController(); + $subuser = factory(Subuser::class)->make(); + $subuser->setRelation('permissions', collect([ + (object) ['permission' => 'some.permission'], + (object) ['permission' => 'another.permission'], + ])); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('subuser', $subuser); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('view-subuser', $server)->once()->andReturnNull(); + $this->repository->shouldReceive('getWithPermissions')->with($subuser)->once()->andReturn($subuser); + + $response = $controller->view($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.users.view', $response); + $this->assertViewHasKey('subuser', $response); + $this->assertViewHasKey('permlist', $response); + $this->assertViewHasKey('permissions', $response); + $this->assertViewKeyEquals('subuser', $subuser, $response); + $this->assertViewKeyEquals('permlist', Permission::getPermissions(), $response); + $this->assertViewKeyEquals('permissions', collect([ + 'some.permission' => true, + 'another.permission' => true, + ]), $response); + } + + /** + * Test the update controller. + */ + public function testUpdateController() + { + $this->setRequestMockClass(SubuserUpdateFormRequest::class); + + $controller = $this->getController(); + $subuser = factory(Subuser::class)->make(); + + $this->setRequestAttribute('subuser', $subuser); + + $this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['some.permission']); + $this->subuserUpdateService->shouldReceive('handle')->with($subuser, ['some.permission'])->once()->andReturnNull(); + $this->alert->shouldReceive('success')->with(trans('server.users.user_updated'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + + $response = $controller->update($this->request, 'abcd1234', $subuser->hashid); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('server.subusers.view', $response, ['uuid' => 'abcd1234', 'id' => $subuser->hashid]); + } + + /** + * Test the create controller. + */ + public function testCreateController() + { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('create-subuser', $server)->once()->andReturnNull(); + + $response = $controller->create($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.users.new', $response); + $this->assertViewHasKey('permissions', $response); + $this->assertViewKeyEquals('permissions', Permission::getPermissions(), $response); + } + + /** + * Test the store controller. + */ + public function testStoreController() + { + $this->setRequestMockClass(SubuserStoreFormRequest::class); + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + + $this->setRequestAttribute('server', $server); + + $this->request->shouldReceive('input')->with('email')->once()->andReturn('user@test.com'); + $this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['some.permission']); + $this->subuserCreationService->shouldReceive('handle')->with($server, 'user@test.com', ['some.permission'])->once()->andReturn($subuser); + $this->alert->shouldReceive('success')->with(trans('server.users.user_assigned'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + + $response = $controller->store($this->request); + $this->assertIsRedirectResponse($response); + $this->assertRedirectRouteEquals('server.subusers.view', $response, [ + 'uuid' => $server->uuid, + 'id' => $subuser->hashid, + ]); + } + + /** + * Test the delete controller. + */ + public function testDeleteController() + { + $controller = $this->getController(); + + $server = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('subuser', $subuser); + + $controller->shouldReceive('authorize')->with('delete-subuser', $server)->once()->andReturnNull(); + $this->subuserDeletionService->shouldReceive('handle')->with($subuser)->once()->andReturnNull(); + + $response = $controller->delete($this->request); + $this->assertIsResponse($response); + $this->assertResponseCodeEquals(204, $response); + } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\SubuserController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(SubuserController::class, [ + $this->alert, + $this->subuserCreationService, + $this->subuserDeletionService, + $this->repository, + $this->subuserUpdateService, + ]); + } +} diff --git a/tests/Unit/Http/Middleware/AdminAuthenticateTest.php b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php new file mode 100644 index 000000000..eee9a6969 --- /dev/null +++ b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php @@ -0,0 +1,57 @@ +make(['root_admin' => 1]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a missing user in the request triggers an error. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testExceptionIsThrownIfUserDoesNotExist() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if the user is not an admin. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testExceptionIsThrownIfUserIsNotAnAdmin() + { + $user = factory(User::class)->make(['root_admin' => 0]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\AdminAuthenticate + */ + private function getMiddleware(): AdminAuthenticate + { + return new AdminAuthenticate(); + } +} diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateIPAccessTest.php new file mode 100644 index 000000000..cf23d0292 --- /dev/null +++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateIPAccessTest.php @@ -0,0 +1,74 @@ +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 = factory(ApiKey::class)->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 testValidIPAganistCIDRRange() + { + $model = factory(ApiKey::class)->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. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testWithInvalidIP() + { + $model = factory(ApiKey::class)->make(['allowed_ips' => ['127.0.0.1']]); + $this->setRequestAttribute('api_key', $model); + + $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('127.0.0.2'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware to be used when testing. + * + * @return \Pterodactyl\Http\Middleware\Api\Application\AuthenticateIPAccess + */ + private function getMiddleware(): AuthenticateIPAccess + { + return new AuthenticateIPAccess(); + } +} diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateKeyTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateKeyTest.php new file mode 100644 index 000000000..486222267 --- /dev/null +++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateKeyTest.php @@ -0,0 +1,126 @@ +auth = m::mock(AuthManager::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(ApiKeyRepositoryInterface::class); + } + + /** + * Test that a missing bearer token will throw an exception. + */ + public function testMissingBearerTokenThrowsException() + { + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(401, $exception->getStatusCode()); + $this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders()); + } + } + + /** + * Test that an invalid API identifer throws an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testInvalidIdentifier() + { + $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234'); + $this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a valid token can continue past the middleware. + */ + public function testValidToken() + { + $model = factory(ApiKey::class)->make(); + + $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['identifier', '=', $model->identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ])->once()->andReturn($model); + $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); + $this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull(); + + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'last_used_at' => Chronos::now(), + ])->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertEquals($model, $this->request->attributes->get('api_key')); + } + + /** + * Test that a valid token identifier with an invalid token attached to it + * triggers an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testInvalidTokenForIdentifier() + { + $model = factory(ApiKey::class)->make(); + + $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf'); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['identifier', '=', $model->identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ])->once()->andReturn($model); + $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware with mocked dependencies for testing. + * + * @return \Pterodactyl\Http\Middleware\Api\Application\AuthenticateKey + */ + private function getMiddleware(): AuthenticateKey + { + return new AuthenticateKey($this->repository, $this->auth, $this->encrypter); + } +} diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php new file mode 100644 index 000000000..56c7f5ffb --- /dev/null +++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php @@ -0,0 +1,53 @@ +setRequestUserModel(null); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a non-admin user results an an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testNonAdminUser() + { + $this->generateRequestUserModel(['root_admin' => false]); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an admin user continues though the middleware. + */ + public function testAdminUser() + { + $this->generateRequestUserModel(['root_admin' => true]); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware for testing. + * + * @return \Pterodactyl\Http\Middleware\Api\Application\AuthenticateUser + */ + private function getMiddleware(): AuthenticateUser + { + return new AuthenticateUser; + } +} diff --git a/tests/Unit/Http/Middleware/Api/Application/SetSessionDriverTest.php b/tests/Unit/Http/Middleware/Api/Application/SetSessionDriverTest.php new file mode 100644 index 000000000..7804f8209 --- /dev/null +++ b/tests/Unit/Http/Middleware/Api/Application/SetSessionDriverTest.php @@ -0,0 +1,69 @@ +appMock = m::mock(Application::class); + $this->config = m::mock(Repository::class); + } + + /** + * Test that a production environment does not try to disable debug bar. + */ + public function testProductionEnvironment() + { + $this->appMock->shouldReceive('environment')->withNoArgs()->once()->andReturn('production'); + $this->config->shouldReceive('set')->with('session.driver', 'array')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a local environment does disable debug bar. + */ + public function testLocalEnvironment() + { + $this->appMock->shouldReceive('environment')->withNoArgs()->once()->andReturn('local'); + $this->appMock->shouldReceive('make')->with(LaravelDebugbar::class)->once()->andReturnSelf(); + $this->appMock->shouldReceive('disable')->withNoArgs()->once()->andReturnNull(); + + $this->config->shouldReceive('set')->with('session.driver', 'array')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware with mocked dependencies for testing. + * + * @return \Pterodactyl\Http\Middleware\Api\Application\SetSessionDriver + */ + private function getMiddleware(): SetSessionDriver + { + return new SetSessionDriver($this->appMock, $this->config); + } +} diff --git a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php new file mode 100644 index 000000000..d60f3eaf2 --- /dev/null +++ b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php @@ -0,0 +1,102 @@ +repository = m::mock(NodeRepositoryInterface::class); + } + + /** + * Test that if we are accessing the daemon.configuration route this middleware is not + * applied in order to allow an unauthenticated request to use a token to grab data. + */ + public function testResponseShouldContinueIfRouteIsExempted() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that not passing in the bearer token will result in a HTTP/401 error with the + * proper response headers. + */ + public function testResponseShouldFailIfNoTokenIsProvided() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(401, $exception->getStatusCode(), 'Assert that a status code of 401 is returned.'); + $this->assertTrue(is_array($exception->getHeaders()), 'Assert that an array of headers is returned.'); + $this->assertArrayHasKey('WWW-Authenticate', $exception->getHeaders(), 'Assert exception headers contains WWW-Authenticate.'); + $this->assertEquals('Bearer', $exception->getHeaders()['WWW-Authenticate']); + } + } + + /** + * Test that passing in an invalid node daemon secret will result in a HTTP/403 + * error response. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testResponseShouldFailIfNoNodeIsFound() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn('test1234'); + + $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', 'test1234']])->once()->andThrow(new RecordNotFoundException); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test a successful middleware process. + */ + public function testSuccessfulMiddlewareProcess() + { + $model = factory(Node::class)->make(); + + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn($model->daemonSecret); + + $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', $model->daemonSecret]])->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('node'); + $this->assertRequestAttributeEquals($model, 'node'); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate + */ + private function getMiddleware(): DaemonAuthenticate + { + return new DaemonAuthenticate($this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/AuthenticateTest.php b/tests/Unit/Http/Middleware/AuthenticateTest.php new file mode 100644 index 000000000..76c7ebc95 --- /dev/null +++ b/tests/Unit/Http/Middleware/AuthenticateTest.php @@ -0,0 +1,40 @@ +request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a logged out user results in an exception. + * + * @expectedException \Illuminate\Auth\AuthenticationException + */ + public function testLoggedOutUser() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Authenticate + */ + private function getMiddleware(): Authenticate + { + return new Authenticate(); + } +} diff --git a/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php new file mode 100644 index 000000000..861e2724c --- /dev/null +++ b/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php @@ -0,0 +1,77 @@ +repository = m::mock(NodeRepositoryInterface::class); + } + + /** + * Test a valid daemon connection. + */ + public function testValidDaemonConnection() + { + $node = factory(Node::class)->make(); + + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name'); + $this->request->shouldReceive('header')->with('X-Access-Node')->twice()->andReturn($node->uuid); + + $this->repository->shouldReceive('findFirstWhere')->with(['daemonSecret' => $node->uuid])->once()->andReturn($node); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('node'); + $this->assertRequestAttributeEquals($node, 'node'); + } + + /** + * Test that ignored routes do not continue through the middleware. + */ + public function testIgnoredRouteShouldContinue() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestMissingAttribute('node'); + } + + /** + * Test that a request missing a X-Access-Node header causes an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testExceptionThrownIfMissingHeader() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name'); + $this->request->shouldReceive('header')->with('X-Access-Node')->once()->andReturn(false); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\DaemonAuthenticate + */ + private function getMiddleware(): DaemonAuthenticate + { + return new DaemonAuthenticate($this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php new file mode 100644 index 000000000..c156665aa --- /dev/null +++ b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php @@ -0,0 +1,53 @@ +appMock = m::mock(Application::class); + $this->config = m::mock(Repository::class); + } + + /** + * Test that a language is defined via the middleware. + */ + public function testLanguageIsSet() + { + $this->config->shouldReceive('get')->with('app.locale', 'en')->once()->andReturn('en'); + $this->appMock->shouldReceive('setLocale')->with('en')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\LanguageMiddleware + */ + private function getMiddleware(): LanguageMiddleware + { + return new LanguageMiddleware($this->appMock, $this->config); + } +} diff --git a/tests/Unit/Http/Middleware/MiddlewareTestCase.php b/tests/Unit/Http/Middleware/MiddlewareTestCase.php new file mode 100644 index 000000000..6356cde20 --- /dev/null +++ b/tests/Unit/Http/Middleware/MiddlewareTestCase.php @@ -0,0 +1,23 @@ +buildRequestMock(); + } +} diff --git a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php new file mode 100644 index 000000000..885108eca --- /dev/null +++ b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php @@ -0,0 +1,60 @@ +authManager = m::mock(AuthManager::class); + } + + /** + * Test that an authenticated user is redirected. + */ + public function testAuthenticatedUserIsRedirected() + { + $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); + $this->authManager->shouldReceive('check')->withNoArgs()->once()->andReturn(true); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('index'), $response->getTargetUrl()); + } + + /** + * Test that a non-authenticated user continues through the middleware. + */ + public function testNonAuthenticatedUserIsNotRedirected() + { + $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); + $this->authManager->shouldReceive('check')->withNoArgs()->once()->andReturn(false); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\RedirectIfAuthenticated + */ + private function getMiddleware(): RedirectIfAuthenticated + { + return new RedirectIfAuthenticated($this->authManager); + } +} diff --git a/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php new file mode 100644 index 000000000..19bd45129 --- /dev/null +++ b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php @@ -0,0 +1,188 @@ +alert = m::mock(AlertsMessageBag::class); + } + + /** + * Test that a missing user does not trigger this middleware. + */ + public function testRequestMissingUser() + { + $this->setRequestUserModel(null); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that the middleware is ignored on specific routes. + * + * @dataProvider ignoredRoutesDataProvider + * @param string $route + */ + public function testRequestOnIgnoredRoute($route) + { + $this->generateRequestUserModel(); + $this->setRequestRouteName($route); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test disabled 2FA requirement. + */ + public function testTwoFactorRequirementDisabled() + { + $this->generateRequestUserModel(); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_NONE); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an invalid value for the level skips the check and continues with the request. + */ + public function testTwoFactorRequirementWithInvalidValue() + { + $this->generateRequestUserModel(); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(333); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for admins as an administrative user who has 2FA disabled. + */ + public function testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled() + { + $user = factory(User::class)->make(['root_admin' => 1, 'use_totp' => 0]); + $this->setRequestUserModel($user); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->alert->shouldReceive('danger')->with(trans('auth.2fa_must_be_enabled'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnSelf(); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('account.security'), $response->getTargetUrl()); + } + + /** + * Test 2FA required for admins as an administrative user who has 2FA enabled. + */ + public function testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled() + { + $user = factory(User::class)->make(['root_admin' => 1, 'use_totp' => 1]); + $this->setRequestUserModel($user); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for admins as an administrative user. + */ + public function testTwoFactorEnabledForAdminsAsNonAdmin() + { + $user = factory(User::class)->make(['root_admin' => 0]); + $this->setRequestUserModel($user); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for all users without 2FA enabled. + */ + public function testTwoFactorEnabledForAllUsersAsUserWith2FADisabled() + { + $user = factory(User::class)->make(['use_totp' => 0]); + $this->setRequestUserModel($user); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_ALL); + + $this->alert->shouldReceive('danger')->with(trans('auth.2fa_must_be_enabled'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnSelf(); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('account.security'), $response->getTargetUrl()); + } + + /** + * Test 2FA required for all users without 2FA enabled. + */ + public function testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled() + { + $user = factory(User::class)->make(['use_totp' => 1]); + $this->setRequestUserModel($user); + $this->setRequestRouteName('random.route'); + $this->setRequirementLevel(RequireTwoFactorAuthentication::LEVEL_ALL); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Routes that should be ignored. + * + * @return array + */ + public function ignoredRoutesDataProvider() + { + return [ + ['account.security'], + ['account.security.revoke'], + ['account.security.totp'], + ['account.security.totp.set'], + ['account.security.totp.disable'], + ['auth.totp'], + ['auth.logout'], + ]; + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication + */ + private function getMiddleware(): RequireTwoFactorAuthentication + { + return new RequireTwoFactorAuthentication($this->alert); + } + + /** + * Set the authentication level requirement. + * + * @param int $level + */ + private function setRequirementLevel(int $level) + { + config()->set('pterodactyl.auth.2fa_required', $level); + } +} diff --git a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php new file mode 100644 index 000000000..8bafebe9f --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php @@ -0,0 +1,150 @@ +config = m::mock(Repository::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->response = m::mock(ResponseFactory::class); + $this->session = m::mock(Session::class); + } + + /** + * Test that an exception is thrown if the request is an API request and the server is suspended. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @expectedExceptionMessage Server is suspended and cannot be accessed. + */ + public function testExceptionIsThrownIfServerIsSuspended() + { + $model = factory(Server::class)->make(['suspended' => 1]); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(true); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if the request is an API request and the server is not installed. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\ConflictHttpException + * @expectedExceptionMessage Server is still completing the installation process. + */ + public function testExceptionIsThrownIfServerIsNotInstalled() + { + $model = factory(Server::class)->make(['installed' => 0]); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(true); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that the correct error pages are rendered depending on the status of the server. + * + * @dataProvider viewDataProvider + */ + public function testCorrectErrorPagesAreRendered(Server $model, string $page, int $httpCode) + { + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false); + $this->config->shouldReceive('get')->with('pterodactyl.json_routes', [])->once()->andReturn([]); + $this->request->shouldReceive('is')->with(...[])->once()->andReturn(false); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + $this->response->shouldReceive('view')->with($page, [], $httpCode)->once()->andReturn(true); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertTrue($response); + } + + /** + * Test that the full middleware works correctly. + */ + public function testValidServerProcess() + { + $model = factory(Server::class)->make(); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false); + $this->config->shouldReceive('get')->with('pterodactyl.json_routes', [])->once()->andReturn([]); + $this->request->shouldReceive('is')->with(...[])->once()->andReturn(false); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + $this->session->shouldReceive('now')->with('server_data.model', $model)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('server'); + $this->assertRequestAttributeEquals($model, 'server'); + } + + /** + * Provide test data that checks that the correct view is returned for each model type. + * + * @return array + */ + public function viewDataProvider(): array + { + // Without this we are unable to instantiate the factory builders for some reason. + $this->refreshApplication(); + + return [ + [factory(Server::class)->make(['suspended' => 1]), 'errors.suspended', 403], + [factory(Server::class)->make(['installed' => 0]), 'errors.installing', 409], + [factory(Server::class)->make(['installed' => 2]), 'errors.installing', 409], + ]; + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\AccessingValidServer + */ + private function getMiddleware(): AccessingValidServer + { + return new AccessingValidServer($this->config, $this->response, $this->repository, $this->session); + } +} diff --git a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php new file mode 100644 index 000000000..8ee1a0e4c --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php @@ -0,0 +1,79 @@ +keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->session = m::mock(Session::class); + } + + /** + * Test a successful instance of the middleware. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $user = $this->setRequestUser(); + $this->setRequestAttribute('server', $model); + + $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andReturn('abc123'); + $this->session->shouldReceive('now')->with('server_data.token', 'abc123')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('server_token'); + $this->assertRequestAttributeEquals('abc123', 'server_token'); + } + + /** + * Test middleware handles missing token exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @expectedExceptionMessage This account does not have permission to access this server. + */ + public function testExceptionIsThrownIfNoTokenIsFound() + { + $model = factory(Server::class)->make(); + $user = $this->setRequestUser(); + $this->setRequestAttribute('server', $model); + + $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andThrow(new RecordNotFoundException); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser + */ + public function getMiddleware(): AuthenticateAsSubuser + { + return new AuthenticateAsSubuser($this->keyProviderService, $this->session); + } +} diff --git a/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php new file mode 100644 index 000000000..856ea1b98 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php @@ -0,0 +1,92 @@ +repository = m::mock(DatabaseRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('database'); + $this->assertRequestAttributeEquals($database, 'database'); + } + + /** + * Test that an exception is thrown if no database record is found. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfNoDatabaseRecordFound() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is found if the database server does not match the + * request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer + */ + private function getMiddleware(): DatabaseBelongsToServer + { + return new DatabaseBelongsToServer($this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php new file mode 100644 index 000000000..755808e06 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php @@ -0,0 +1,81 @@ +hashids = m::mock(HashidsInterface::class); + $this->repository = m::mock(ScheduleRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $schedule = factory(Schedule::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id); + $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('schedule'); + $this->assertRequestAttributeEquals($schedule, 'schedule'); + } + + /** + * Test that an exception is thrown if the schedule does not belong to + * the request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfScheduleDoesNotBelongToServer() + { + $model = factory(Server::class)->make(); + $schedule = factory(Schedule::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id); + $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer + */ + private function getMiddleware(): ScheduleBelongsToServer + { + return new ScheduleBelongsToServer($this->hashids, $this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php new file mode 100644 index 000000000..a4cd7e4f7 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php @@ -0,0 +1,156 @@ +hashids = m::mock(HashidsInterface::class); + $this->repository = m::mock(SubuserRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('GET'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('subuser'); + $this->assertRequestAttributeEquals($subuser, 'subuser'); + } + + /** + * Test that a user can edit a user other than themselves. + */ + public function testSuccessfulMiddlewareWhenPatchRequest() + { + $this->setRequestUser(); + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('subuser'); + $this->assertRequestAttributeEquals($subuser, 'subuser'); + } + + /** + * Test that an exception is thrown if a user attempts to edit themself. + */ + public function testExceptionIsThrownIfUserTriesToEditSelf() + { + $user = $this->setRequestUser(); + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + 'user_id' => $user->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH'); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.subusers.editing_self'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if a subuser server does not match the + * request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if no subuser is found. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfNoSubuserIsFound() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer + */ + private function getMiddleware(): SubuserBelongsToServer + { + return new SubuserBelongsToServer($this->hashids, $this->repository); + } +} diff --git a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php new file mode 100644 index 000000000..a398f969f --- /dev/null +++ b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php @@ -0,0 +1,233 @@ +commandRepository = m::mock(CommandRepositoryInterface::class); + $this->config = m::mock(Repository::class); + $this->keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->powerRepository = m::mock(PowerRepositoryInterface::class); + $this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class); + $this->taskRepository = m::mock(TaskRepositoryInterface::class); + + $this->app->instance(Repository::class, $this->config); + $this->app->instance(TaskRepositoryInterface::class, $this->taskRepository); + $this->app->instance(ScheduleRepositoryInterface::class, $this->scheduleRepository); + } + + /** + * Test power option passed to job. + */ + public function testPowerAction() + { + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); + $this->powerRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('sendSignal')->with($task->payload)->once()->andReturn(new Response); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull(); + + $this->scheduleRepository->shouldReceive('withoutFreshModel->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertNotDispatched(RunTaskJob::class); + } + + /** + * Test commmand action passed to job. + */ + public function testCommandAction() + { + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); + $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('send')->with($task->payload)->once()->andReturn(new Response); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull(); + + $this->scheduleRepository->shouldReceive('withoutFreshModel->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertNotDispatched(RunTaskJob::class); + } + + /** + * Test that the next task in the list is queued if the current one is not the last. + */ + public function testNextTaskQueuedIfExists() + { + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); + $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() + ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('send')->with($task->payload)->once()->andReturn(new Response); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + + $nextTask = factory(Task::class)->make(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturn($nextTask); + $this->taskRepository->shouldReceive('update')->with($nextTask->id, [ + 'is_queued' => true, + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertDispatched(RunTaskJob::class, function ($job) use ($nextTask, $schedule) { + $this->assertEquals($nextTask->id, $job->task, 'Assert correct task ID is passed to job.'); + $this->assertEquals($schedule->id, $job->schedule, 'Assert correct schedule ID is passed to job.'); + $this->assertEquals($nextTask->time_offset, $job->delay, 'Assert correct job delay time is set.'); + + return true; + }); + } + + /** + * Test that an exception is thrown if an invalid task action is supplied. + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot run a task that points to a non-existent action. + */ + public function testInvalidActionPassedToJob() + { + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + + $this->getJobInstance($task->id, 1234); + } + + /** + * Test that a schedule marked as disabled does not get processed. + */ + public function testScheduleMarkedAsDisabledDoesNotProcess() + { + $schedule = factory(Schedule::class)->make(['is_active' => false]); + $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + + $this->scheduleRepository->shouldReceive('withoutFreshModel->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturn(1); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturn(1); + + $this->getJobInstance($task->id, $schedule->id); + $this->assertTrue(true); + } + + /** + * Run the job using the mocks provided. + * + * @param int $task + * @param int $schedule + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function getJobInstance($task, $schedule) + { + return (new RunTaskJob($task, $schedule))->handle( + $this->commandRepository, + $this->keyProviderService, + $this->powerRepository, + $this->taskRepository + ); + } +} diff --git a/tests/Unit/Rules/UsernameTest.php b/tests/Unit/Rules/UsernameTest.php new file mode 100644 index 000000000..fa21530af --- /dev/null +++ b/tests/Unit/Rules/UsernameTest.php @@ -0,0 +1,69 @@ +assertTrue((new Username)->passes('test', $username), 'Assert username is valid.'); + } + + /** + * Test invalid usernames return false. + * + * @dataProvider invalidUsernameDataProvider + */ + public function testInvalidUsernames(string $username) + { + $this->assertFalse((new Username)->passes('test', $username), 'Assert username is not valid.'); + } + + /** + * Provide valid usernames. + * @return array + */ + public function validUsernameDataProvider(): array + { + return [ + ['username'], + ['user_name'], + ['user.name'], + ['user-name'], + ['123username123'], + ['123-user.name'], + ['123456'], + ]; + } + + /** + * Provide invalid usernames. + * + * @return array + */ + public function invalidUsernameDataProvider(): array + { + return [ + ['_username'], + ['username_'], + ['_username_'], + ['-username'], + ['.username'], + ['username-'], + ['username.'], + ['user*name'], + ['user^name'], + ['user#name'], + ['user+name'], + ['1234_'], + ]; + } +} diff --git a/tests/Unit/Services/Acl/Api/AdminAclTest.php b/tests/Unit/Services/Acl/Api/AdminAclTest.php new file mode 100644 index 000000000..2f2f07641 --- /dev/null +++ b/tests/Unit/Services/Acl/Api/AdminAclTest.php @@ -0,0 +1,48 @@ +assertSame($outcome, AdminAcl::can($permission, $check)); + } + + /** + * Test that checking aganist a model works as expected. + */ + public function testCheck() + { + $model = factory(ApiKey::class)->make(['r_servers' => AdminAcl::READ | AdminAcl::WRITE]); + + $this->assertTrue(AdminAcl::check($model, AdminAcl::RESOURCE_SERVERS, AdminAcl::WRITE)); + } + + /** + * Provide valid and invalid permissions combos for testing. + * + * @return array + */ + public function permissionsDataProvider(): array + { + return [ + [AdminAcl::READ, AdminAcl::READ, true], + [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::READ, true], + [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::WRITE, true], + [AdminAcl::WRITE, AdminAcl::WRITE, true], + [AdminAcl::READ, AdminAcl::WRITE, false], + [AdminAcl::NONE, AdminAcl::READ, false], + [AdminAcl::NONE, AdminAcl::WRITE, false], + ]; + } +} diff --git a/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php b/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php new file mode 100644 index 000000000..cded75b28 --- /dev/null +++ b/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php @@ -0,0 +1,59 @@ +repository = m::mock(AllocationRepositoryInterface::class); + } + + /** + * Test that an allocation is deleted. + */ + public function testAllocationIsDeleted() + { + $model = factory(Allocation::class)->make(); + + $this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1); + + $response = $this->getService()->handle($model); + $this->assertEquals(1, $response); + } + + /** + * Test that an exception gets thrown if an allocation is currently assigned to a server. + * + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException + */ + public function testExceptionThrownIfAssignedToServer() + { + $model = factory(Allocation::class)->make(['server_id' => 123]); + + $this->getService()->handle($model); + } + + /** + * Return an instance of the service with mocked injections. + * + * @return \Pterodactyl\Services\Allocations\AllocationDeletionService + */ + private function getService(): AllocationDeletionService + { + return new AllocationDeletionService($this->repository); + } +} diff --git a/tests/Unit/Services/Allocations/AssignmentServiceTest.php b/tests/Unit/Services/Allocations/AssignmentServiceTest.php new file mode 100644 index 000000000..805b789a3 --- /dev/null +++ b/tests/Unit/Services/Allocations/AssignmentServiceTest.php @@ -0,0 +1,311 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Allocations; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Models\Node; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Allocations\AssignmentService; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; + +class AssignmentServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Models\Node + */ + protected $node; + + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Allocations\AssignmentService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + // Due to a bug in PHP, this is necessary since we only have a single test + // that relies on this mock. If this does not exist the test will fail to register + // correctly. + // + // This can also be avoided if tests were run in isolated processes, or if that test + // came first, but neither of those are good solutions, so this is the next best option. + PHPMock::defineFunctionMock('\\Pterodactyl\\Services\\Allocations', 'gethostbyname'); + + $this->node = factory(Node::class)->make(); + $this->connection = m::mock(ConnectionInterface::class); + $this->repository = m::mock(AllocationRepositoryInterface::class); + + $this->service = new AssignmentService($this->repository, $this->connection); + } + + /** + * Test a non-CIDR notated IP address without a port range. + */ + public function testIndividualIpAddressWithoutRange() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['1024'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node->id, $data); + } + + /** + * Test a non-CIDR IP address with a port range provided. + */ + public function testIndividualIpAddressWithRange() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['1024-1026'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1025, + 'ip_alias' => null, + 'server_id' => null, + ], + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1026, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node->id, $data); + } + + /** + * Test a non-CIRD IP address with a single port and an alias. + */ + public function testIndividualIPAddressWithAlias() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['1024'], + 'allocation_alias' => 'my.alias.net', + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1024, + 'ip_alias' => 'my.alias.net', + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node->id, $data); + } + + /** + * Test that a domain name can be passed in place of an IP address. + */ + public function testDomainNamePassedInPlaceOfIPAddress() + { + $data = [ + 'allocation_ip' => 'test-domain.com', + 'allocation_ports' => ['1024'], + ]; + + $this->getFunctionMock('\\Pterodactyl\\Services\\Allocations', 'gethostbyname') + ->expects($this->once())->willReturn('192.168.1.1'); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node->id, $data); + } + + /** + * Test that a CIDR IP address without a range works properly. + */ + public function testCIDRNotatedIPAddressWithoutRange() + { + $data = [ + 'allocation_ip' => '192.168.1.100/31', + 'allocation_ports' => ['1024'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.100', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.101', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node->id, $data); + } + + /** + * Test that a CIDR IP address with a range works properly. + */ + public function testCIDRNotatedIPAddressOutsideRangeLimit() + { + $data = [ + 'allocation_ip' => '192.168.1.100/20', + 'allocation_ports' => ['1024'], + ]; + + try { + $this->service->handle($this->node->id, $data); + } catch (Exception $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.allocations.cidr_out_of_range'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if there are too many ports. + */ + public function testAllocationWithPortsExceedingLimit() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['5000-7000'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + + try { + $this->service->handle($this->node->id, $data); + } catch (Exception $exception) { + if (! $exception instanceof DisplayException) { + throw $exception; + } + + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.allocations.too_many_ports'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if an invalid port is provided. + */ + public function testInvalidPortProvided() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['test123'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + + try { + $this->service->handle($this->node->id, $data); + } catch (Exception $exception) { + if (! $exception instanceof DisplayException) { + throw $exception; + } + + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.allocations.invalid_mapping', ['port' => 'test123']), $exception->getMessage()); + } + } + + /** + * Test that a model can be passed in place of an ID. + */ + public function testModelCanBePassedInPlaceOfNodeModel() + { + $data = [ + 'allocation_ip' => '192.168.1.1', + 'allocation_ports' => ['1024'], + ]; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->with([ + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1024, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($this->node, $data); + } +} diff --git a/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php b/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php new file mode 100644 index 000000000..e382a8636 --- /dev/null +++ b/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php @@ -0,0 +1,156 @@ +connection = m::mock(ConnectionInterface::class); + $this->daemonRepository = m::mock(DaemonRepositoryInterface::class); + $this->repository = m::mock(AllocationRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + } + + /** + * Test that an allocation can be updated. + * + * @dataProvider useModelDataProvider + */ + public function testAllocationIsUpdated(bool $useModel) + { + $allocations = factory(Allocation::class)->times(2)->make(); + $model = factory(Server::class)->make(); + if (! $useModel) { + $this->serverRepository->shouldReceive('find')->with(1234)->once()->andReturn($model); + } + + $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn($allocations); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->serverRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->serverRepository->shouldReceive('update')->with($model->id, [ + 'allocation_id' => $allocations->first()->id, + ])->once()->andReturn(new Response); + + $this->daemonRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); + $this->daemonRepository->shouldReceive('update')->with([ + 'build' => [ + 'default' => [ + 'ip' => $allocations->first()->ip, + 'port' => $allocations->first()->port, + ], + 'ports|overwrite' => $allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + ], + ])->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($useModel ? $model : 1234, $allocations->first()->id); + $this->assertNotEmpty($response); + $this->assertSame($allocations->first(), $response); + } + + /** + * Test that an allocation that doesn't belong to a server throws an exception. + * + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException + */ + public function testAllocationNotBelongingToServerThrowsException() + { + $model = factory(Server::class)->make(); + $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn(collect()); + + $this->getService()->handle($model, 1234); + } + + /** + * Test that an exception thrown by guzzle is handled properly. + */ + public function testExceptionThrownByGuzzleIsHandled() + { + $this->configureExceptionMock(); + + $allocation = factory(Allocation::class)->make(); + $model = factory(Server::class)->make(); + + $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn(collect([$allocation])); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->serverRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->serverRepository->shouldReceive('update')->with($model->id, [ + 'allocation_id' => $allocation->id, + ])->once()->andReturn(new Response); + + $this->daemonRepository->shouldReceive('setServer->update')->once()->andThrow($this->getExceptionMock()); + $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getService()->handle($model, $allocation->id); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } + + /** + * Data provider to determine if a model should be passed or an int. + * + * @return array + */ + public function useModelDataProvider(): array + { + return [[false], [true]]; + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Allocations\SetDefaultAllocationService + */ + private function getService(): SetDefaultAllocationService + { + return new SetDefaultAllocationService($this->repository, $this->connection, $this->daemonRepository, $this->serverRepository); + } +} diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php new file mode 100644 index 000000000..613be35f8 --- /dev/null +++ b/tests/Unit/Services/Api/KeyCreationServiceTest.php @@ -0,0 +1,171 @@ +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 = factory(ApiKey::class)->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 = factory(ApiKey::class)->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 = factory(ApiKey::class)->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 = factory(ApiKey::class)->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. + * + * @return array + */ + 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. + * + * @return \Pterodactyl\Services\Api\KeyCreationService + */ + private function getService(): KeyCreationService + { + return new KeyCreationService($this->repository, $this->encrypter); + } +} diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php new file mode 100644 index 000000000..8efcb2f42 --- /dev/null +++ b/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Services\DaemonKeys; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyCreationServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \Carbon\Carbon|\Mockery\Mock + */ + protected $carbon; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->carbon = m::mock(Carbon::class); + $this->config = m::Mock(Repository::class); + $this->repository = m::mock(DaemonKeyRepositoryInterface::class); + + $this->service = new DaemonKeyCreationService($this->carbon, $this->config, $this->repository); + } + + /** + * Test that a daemon key is created. + */ + public function testDaemonKeyIsCreated() + { + $this->getFunctionMock('\\Pterodactyl\\Services\\DaemonKeys', 'str_random') + ->expects($this->once())->willReturn('random_string'); + + $this->config->shouldReceive('get')->with('pterodactyl.api.key_expire_time')->once()->andReturn(100); + $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('addMinutes')->with(100)->once()->andReturnSelf() + ->shouldReceive('toDateTimeString')->withNoArgs()->once()->andReturn('00:00:00'); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('create')->with([ + 'user_id' => 1, + 'server_id' => 2, + 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . 'random_string', + 'expires_at' => '00:00:00', + ])->once()->andReturnNull(); + + $response = $this->service->handle(2, 1); + $this->assertNotEmpty($response); + $this->assertEquals('i_random_string', $response); + } +} diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyDeletionServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyDeletionServiceTest.php new file mode 100644 index 000000000..74007dda6 --- /dev/null +++ b/tests/Unit/Services/DaemonKeys/DaemonKeyDeletionServiceTest.php @@ -0,0 +1,163 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\DaemonKeys; + +use Mockery as m; +use Tests\TestCase; +use Illuminate\Log\Writer; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\DaemonKey; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class DaemonKeyDeletionServiceTest extends TestCase +{ + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock + */ + protected $daemonRepository; + + /** + * @var \GuzzleHttp\Exception\RequestException|\Mockery\Mock + */ + protected $exception; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService + */ + protected $service; + + /** + * @var \Illuminate\Log\Writer|\Mockery\Mock + */ + protected $writer; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->exception = m::mock(RequestException::class); + $this->repository = m::mock(DaemonKeyRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->writer = m::mock(Writer::class); + + $this->service = new DaemonKeyDeletionService( + $this->connection, + $this->repository, + $this->daemonRepository, + $this->serverRepository, + $this->writer + ); + } + + /** + * Test that a daemon key is deleted correctly. + */ + public function testKeyIsDeleted() + { + $server = factory(Server::class)->make(); + $key = factory(DaemonKey::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', 100], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $this->repository->shouldReceive('delete')->with($key->id)->once()->andReturn(1); + $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('revokeAccessKey')->with($key->secret)->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($server, 100); + $this->assertTrue(true); + } + + /** + * Test that a daemon key can be deleted when only a server ID is passed. + */ + public function testKeyIsDeletedIfIdIsPassedInPlaceOfModel() + { + $server = factory(Server::class)->make(); + $key = factory(DaemonKey::class)->make(); + + $this->serverRepository->shouldReceive('find')->with($server->id)->once()->andReturn($server); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', 100], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $this->repository->shouldReceive('delete')->with($key->id)->once()->andReturn(1); + $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf() + ->shouldReceive('revokeAccessKey')->with($key->secret)->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($server->id, 100); + $this->assertTrue(true); + } + + /** + * Test that an exception is properly handled if thrown by guzzle. + */ + public function testExceptionReturnedByGuzzleIsHandled() + { + $server = factory(Server::class)->make(); + $key = factory(DaemonKey::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', 100], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $this->repository->shouldReceive('delete')->with($key->id)->once()->andReturn(1); + $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andThrow($this->exception); + $this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); + $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); + + try { + $this->service->handle($server, 100); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('admin/server.exceptions.daemon_exception', [ + 'code' => 'E_CONN_REFUSED', + ]), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php new file mode 100644 index 000000000..87d5f506b --- /dev/null +++ b/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php @@ -0,0 +1,184 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\DaemonKeys; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\DaemonKey; +use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyProviderServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService|\Mockery\Mock + */ + private $keyCreationService; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService|\Mockery\Mock + */ + private $keyUpdateService; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + Carbon::setTestNow(Carbon::now()); + + $this->keyCreationService = m::mock(DaemonKeyCreationService::class); + $this->keyUpdateService = m::mock(DaemonKeyUpdateService::class); + $this->repository = m::mock(DaemonKeyRepositoryInterface::class); + } + + /** + * Test that a key is returned correctly as a non-admin. + */ + public function testKeyIsReturned() + { + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(); + $key = factory(DaemonKey::class)->make(); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $response = $this->getService()->handle($server, $user); + $this->assertNotEmpty($response); + $this->assertEquals($key->secret, $response); + } + + /** + * Test that an expired key is updated and then returned. + */ + public function testExpiredKeyIsUpdated() + { + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(['root_admin' => 0]); + $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $this->keyUpdateService->shouldReceive('handle')->with($key->id)->once()->andReturn('abc123'); + + $response = $this->getService()->handle($server, $user); + $this->assertNotEmpty($response); + $this->assertEquals('abc123', $response); + } + + /** + * Test that an expired key is not updated and the expired key is returned. + */ + public function testExpiredKeyIsNotUpdated() + { + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(['root_admin' => 0]); + $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andReturn($key); + + $response = $this->getService()->handle($server, $user, false); + $this->assertNotEmpty($response); + $this->assertEquals($key->secret, $response); + } + + /** + * Test that a key is created if it is missing and the user is a + * root administrator. + */ + public function testMissingKeyIsCreatedIfRootAdmin() + { + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(['root_admin' => 1]); + $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andThrow(new RecordNotFoundException); + + $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret); + + $response = $this->getService()->handle($server, $user, false); + $this->assertNotEmpty($response); + $this->assertEquals($key->secret, $response); + } + + /** + * Test that a key is created if it is missing and the user is the + * server owner. + */ + public function testMissingKeyIsCreatedIfUserIsServerOwner() + { + $user = factory(User::class)->make(['root_admin' => 0]); + $server = factory(Server::class)->make(['owner_id' => $user->id]); + $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andThrow(new RecordNotFoundException); + + $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret); + + $response = $this->getService()->handle($server, $user, false); + $this->assertNotEmpty($response); + $this->assertEquals($key->secret, $response); + } + + /** + * Test that an exception is thrown if the user should not get a key. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfUserDoesNotDeserveKey() + { + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(['root_admin' => 0]); + + $this->repository->shouldReceive('findFirstWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andThrow(new RecordNotFoundException); + + $this->getService()->handle($server, $user, false); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService + */ + private function getService(): DaemonKeyProviderService + { + return new DaemonKeyProviderService($this->keyCreationService, $this->repository, $this->keyUpdateService); + } +} diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php new file mode 100644 index 000000000..badc29bf1 --- /dev/null +++ b/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\DaemonKeys; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; +use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; + +class DaemonKeyUpdateServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \Carbon\Carbon|\Mockery\Mock + */ + protected $carbon; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->carbon = m::Mock(Carbon::class); + $this->config = m::mock(Repository::class); + $this->repository = m::mock(DaemonKeyRepositoryInterface::class); + + $this->service = new DaemonKeyUpdateService($this->carbon, $this->config, $this->repository); + } + + /** + * Test that a key is updated. + */ + public function testKeyIsUpdated() + { + $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . 'random_string'; + + $this->getFunctionMock('\\Pterodactyl\\Services\\DaemonKeys', 'str_random') + ->expects($this->once())->with(40)->willReturn('random_string'); + + $this->config->shouldReceive('get')->with('pterodactyl.api.key_expire_time')->once()->andReturn(100); + $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('addMinutes')->with(100)->once()->andReturnSelf() + ->shouldReceive('toDateTimeString')->withNoArgs()->once()->andReturn('00:00:00'); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->repository->shouldReceive('update')->with(123, [ + 'secret' => $secret, + 'expires_at' => '00:00:00', + ])->once()->andReturnNull(); + + $response = $this->service->handle(123); + $this->assertNotEmpty($response); + $this->assertEquals($secret, $response); + } +} diff --git a/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php b/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php new file mode 100644 index 000000000..dbc20d577 --- /dev/null +++ b/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php @@ -0,0 +1,116 @@ +daemonRepository = m::mock(ServerRepositoryInterface::class); + $this->repository = m::mock(DaemonKeyRepositoryInterface::class); + } + + /** + * Test that keys can be successfully revoked. + */ + public function testSuccessfulKeyRevocation() + { + $user = factory(User::class)->make(); + $node = factory(Node::class)->make(); + $key = factory(DaemonKey::class)->make(['user_id' => $user->id]); + $key->setRelation('node', $node); + + $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key])); + $this->daemonRepository->shouldReceive('setNode')->with($node)->once()->andReturnSelf(); + $this->daemonRepository->shouldReceive('revokeAccessKey')->with([$key->secret])->once()->andReturn(new Response); + + $this->repository->shouldReceive('deleteKeys')->with([$key->id])->once()->andReturnNull(); + + $this->getService()->handle($user); + $this->assertTrue(true); + } + + /** + * Test that an exception thrown by a call to the daemon is handled. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionThrownFromDaemonCallIsHandled() + { + $this->configureExceptionMock(); + + $user = factory(User::class)->make(); + $node = factory(Node::class)->make(); + $key = factory(DaemonKey::class)->make(['user_id' => $user->id]); + $key->setRelation('node', $node); + + $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key])); + $this->daemonRepository->shouldReceive('setNode->revokeAccessKey')->with([$key->secret])->once()->andThrow($this->getExceptionMock()); + + $this->getService()->handle($user); + } + + /** + * Test that the behavior for handling exceptions that should not be thrown + * immediately is working correctly and adds them to the array. + */ + public function testIgnoredExceptionsAreHandledProperly() + { + $this->configureExceptionMock(); + + $user = factory(User::class)->make(); + $node = factory(Node::class)->make(); + $key = factory(DaemonKey::class)->make(['user_id' => $user->id]); + $key->setRelation('node', $node); + + $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key])); + $this->daemonRepository->shouldReceive('setNode->revokeAccessKey')->with([$key->secret])->once()->andThrow($this->getExceptionMock()); + + $this->repository->shouldReceive('deleteKeys')->with([$key->id])->once()->andReturnNull(); + + $service = $this->getService(); + $service->handle($user, true); + $this->assertNotEmpty($service->getExceptions()); + $this->assertArrayHasKey($node->id, $service->getExceptions()); + $this->assertSame(array_get($service->getExceptions(), $node->id), $this->getExceptionMock()); + $this->assertTrue(true); + } + + /** + * Return an instance of the service for testing. + * + * @return \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService + */ + private function getService(): RevokeMultipleDaemonKeysService + { + return new RevokeMultipleDaemonKeysService($this->repository, $this->daemonRepository); + } +} diff --git a/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php new file mode 100644 index 000000000..946f977d4 --- /dev/null +++ b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php @@ -0,0 +1,99 @@ +connection = m::mock(ConnectionInterface::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseRepositoryInterface::class); + } + + /** + * Test that a password can be updated. + * + * @dataProvider useModelDataProvider + */ + public function testPasswordIsChanged(bool $useModel) + { + $model = factory(Database::class)->make(); + + if (! $useModel) { + $this->repository->shouldReceive('find')->with(1234)->once()->andReturn($model); + } + + $this->dynamic->shouldReceive('set')->with('dynamic', $model->database_host_id)->once()->andReturnNull(); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->repository->shouldReceive('update')->with($model->id, ['password' => 'enc123'])->once()->andReturn(true); + + $this->repository->shouldReceive('dropUser')->with($model->username, $model->remote)->once()->andReturn(true); + $this->repository->shouldReceive('createUser')->with($model->username, $model->remote, 'test123')->once()->andReturn(true); + $this->repository->shouldReceive('assignUserToDatabase')->with($model->database, $model->username, $model->remote)->once()->andReturn(true); + $this->repository->shouldReceive('flush')->withNoArgs()->once()->andReturn(true); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturn(true); + + $response = $this->getService()->handle($useModel ? $model : 1234, 'test123'); + $this->assertNotEmpty($response); + $this->assertTrue($response); + } + + /** + * Data provider to determine if a model should be passed or an int. + * + * @return array + */ + public function useModelDataProvider(): array + { + return [[false], [true]]; + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\DatabasePasswordService + */ + private function getService(): DatabasePasswordService + { + return new DatabasePasswordService($this->connection, $this->repository, $this->dynamic, $this->encrypter); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php new file mode 100644 index 000000000..603b871a0 --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php @@ -0,0 +1,101 @@ +connection = m::mock(ConnectionInterface::class); + $this->databaseManager = m::mock(DatabaseManager::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a database host can be created. + */ + public function testDatabaseHostIsCreated() + { + $model = factory(DatabaseHost::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + $this->repository->shouldReceive('create')->with(m::subset([ + 'password' => 'enc123', + 'username' => $model->username, + 'node_id' => $model->node_id, + ]))->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle([ + 'password' => 'test123', + 'username' => $model->username, + 'node_id' => $model->node_id, + ]); + + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostCreationService + */ + private function getService(): HostCreationService + { + return new HostCreationService( + $this->connection, + $this->databaseManager, + $this->repository, + $this->dynamic, + $this->encrypter + ); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php new file mode 100644 index 000000000..bd927b8e8 --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php @@ -0,0 +1,85 @@ +databaseRepository = m::mock(DatabaseRepositoryInterface::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a host can be deleted. + */ + public function testHostIsDeleted() + { + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1234)->once()->andReturn(1); + + $response = $this->getService()->handle(1234); + $this->assertNotEmpty($response); + $this->assertSame(1, $response); + } + + /** + * Test that an exception is thrown if a host with databases is deleted. + * + * @dataProvider databaseCountDataProvider + */ + public function testExceptionIsThrownIfDeletingHostWithDatabases(int $count) + { + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn($count); + + try { + $this->getService()->handle(1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(HasActiveServersException::class, $exception); + $this->assertEquals(trans('exceptions.databases.delete_has_databases'), $exception->getMessage()); + } + } + + /** + * Data provider to ensure exceptions are thrown for any value > 0. + * + * @return array + */ + public function databaseCountDataProvider(): array + { + return [[1], [2], [10]]; + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostDeletionService + */ + private function getService(): HostDeletionService + { + return new HostDeletionService($this->databaseRepository, $this->repository); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php new file mode 100644 index 000000000..7e115c000 --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php @@ -0,0 +1,112 @@ +connection = m::mock(ConnectionInterface::class); + $this->databaseManager = m::mock(DatabaseManager::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a password is encrypted before storage if provided. + */ + public function testPasswordIsEncryptedWhenProvided() + { + $model = factory(DatabaseHost::class)->make(); + + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with(1234, ['password' => 'enc123'])->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle(1234, ['password' => 'test123']); + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Test that updates still occur when no password is provided. + */ + public function testUpdateOccursWhenNoPasswordIsProvided() + { + $model = factory(DatabaseHost::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with(1234, ['username' => 'test'])->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle(1234, ['password' => '', 'username' => 'test']); + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostUpdateService + */ + private function getService(): HostUpdateService + { + return new HostUpdateService( + $this->connection, + $this->databaseManager, + $this->repository, + $this->dynamic, + $this->encrypter + ); + } +} diff --git a/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php b/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php new file mode 100644 index 000000000..1dd124ab4 --- /dev/null +++ b/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php @@ -0,0 +1,90 @@ +repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggConfigurationService($this->repository); + } + + /** + * Test that the correct array is returned. + */ + public function testCorrectArrayIsReturned() + { + $egg = factory(Egg::class)->make([ + 'config_startup' => '{"test": "start"}', + 'config_stop' => 'test', + 'config_files' => '{"test": "file"}', + 'config_logs' => '{"test": "logs"}', + ]); + + $response = $this->service->handle($egg); + $this->assertNotEmpty($response); + $this->assertTrue(is_array($response), 'Assert response is an array.'); + $this->assertArrayHasKey('startup', $response); + $this->assertArrayHasKey('stop', $response); + $this->assertArrayHasKey('configs', $response); + $this->assertArrayHasKey('log', $response); + $this->assertArrayHasKey('query', $response); + $this->assertEquals('start', object_get($response['startup'], 'test')); + $this->assertEquals('test', 'test'); + $this->assertEquals('file', object_get($response['configs'], 'test')); + $this->assertEquals('logs', object_get($response['log'], 'test')); + $this->assertEquals('none', $response['query']); + } + + /** + * Test that an integer referencing a model can be passed in place of the model. + */ + public function testFunctionHandlesIntegerPassedInPlaceOfModel() + { + $egg = factory(Egg::class)->make([ + 'config_startup' => '{"test": "start"}', + 'config_stop' => 'test', + 'config_files' => '{"test": "file"}', + 'config_logs' => '{"test": "logs"}', + ]); + + $this->repository->shouldReceive('getWithCopyAttributes')->with($egg->id)->once()->andReturn($egg); + + $response = $this->service->handle($egg->id); + $this->assertNotEmpty($response); + $this->assertTrue(is_array($response), 'Assert response is an array.'); + $this->assertArrayHasKey('startup', $response); + $this->assertArrayHasKey('stop', $response); + $this->assertArrayHasKey('configs', $response); + $this->assertArrayHasKey('log', $response); + $this->assertArrayHasKey('query', $response); + $this->assertEquals('start', object_get($response['startup'], 'test')); + $this->assertEquals('test', 'test'); + $this->assertEquals('file', object_get($response['configs'], 'test')); + $this->assertEquals('logs', object_get($response['log'], 'test')); + $this->assertEquals('none', $response['query']); + } +} diff --git a/tests/Unit/Services/Eggs/EggCreationServiceTest.php b/tests/Unit/Services/Eggs/EggCreationServiceTest.php new file mode 100644 index 000000000..7afa07871 --- /dev/null +++ b/tests/Unit/Services/Eggs/EggCreationServiceTest.php @@ -0,0 +1,146 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Tests\Traits\MocksUuids; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\EggCreationService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggCreationServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\EggCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggCreationService($this->config, $this->repository); + } + + /** + * Test that a new model is created when not using the config from attribute. + */ + public function testCreateNewModelWithoutUsingConfigFrom() + { + $model = factory(Egg::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + 'config_from' => null, + 'name' => $model->name, + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle(['name' => $model->name]); + + $this->assertNotEmpty($response); + $this->assertNull(object_get($response, 'config_from')); + $this->assertEquals($model->name, $response->name); + } + + /** + * Test that a new model is created when using the config from attribute. + */ + public function testCreateNewModelUsingConfigFrom() + { + $model = factory(Egg::class)->make(); + + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $model->nest_id], + ['id', '=', 12345], + ])->once()->andReturn(1); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'nest_id' => $model->nest_id, + 'config_from' => 12345, + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle([ + 'nest_id' => $model->nest_id, + 'config_from' => 12345, + ]); + + $this->assertNotEmpty($response); + $this->assertEquals($response, $model); + } + + /** + * Test that certain data, such as the UUID or author takes priority over data + * that is passed into the function. + */ + public function testDataProvidedByHandlerTakesPriorityOverPassedData() + { + $model = factory(Egg::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + 'config_from' => null, + 'name' => $model->name, + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle(['name' => $model->name, 'uuid' => 'should-be-ignored', 'author' => 'should-be-ignored']); + + $this->assertNotEmpty($response); + $this->assertNull(object_get($response, 'config_from')); + $this->assertEquals($model->name, $response->name); + } + + /** + * Test that an exception is thrown if no parent configuration can be located. + */ + public function testExceptionIsThrownIfNoParentConfigurationIsFound() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', null], + ['id', '=', 1], + ])->once()->andReturn(0); + + try { + $this->service->handle(['config_from' => 1]); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/EggDeletionServiceTest.php b/tests/Unit/Services/Eggs/EggDeletionServiceTest.php new file mode 100644 index 000000000..c3ef3ef1b --- /dev/null +++ b/tests/Unit/Services/Eggs/EggDeletionServiceTest.php @@ -0,0 +1,93 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\EggDeletionService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\HasChildrenException; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class EggDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Eggs\EggDeletionService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(EggRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + + $this->service = new EggDeletionService($this->serverRepository, $this->repository); + } + + /** + * Test that Egg is deleted if no servers are found. + */ + public function testEggIsDeletedIfNoServersAreFound() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle(1)); + } + + /** + * Test that Egg is not deleted if servers are found. + */ + public function testExceptionIsThrownIfServersAreFound() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(1); + + try { + $this->service->handle(1); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(HasActiveServersException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.delete_has_servers'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if children Eggs exist. + */ + public function testExceptionIsThrownIfChildrenArePresent() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(1); + + try { + $this->service->handle(1); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(HasChildrenException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.has_children'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/EggUpdateServiceTest.php b/tests/Unit/Services/Eggs/EggUpdateServiceTest.php new file mode 100644 index 000000000..5e1d29bb4 --- /dev/null +++ b/tests/Unit/Services/Eggs/EggUpdateServiceTest.php @@ -0,0 +1,112 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Services\Eggs\EggUpdateService; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Models\Egg + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\EggUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(Egg::class)->make(); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggUpdateService($this->repository); + } + + /** + * Test that an Egg is updated when no config_from attribute is passed. + */ + public function testEggIsUpdatedWhenNoConfigFromIsProvided() + { + $this->repository->shouldReceive('withoutFreshModel->update') + ->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model, ['test_field' => 'field_value']); + + $this->assertTrue(true); + } + + /** + * Test that Egg is updated when a valid config_from attribute is passed. + */ + public function testOptionIsUpdatedWhenValidConfigFromIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $this->model->nest_id], + ['id', '=', 1], + ])->once()->andReturn(1); + + $this->repository->shouldReceive('withoutFreshModel->update') + ->with($this->model->id, ['config_from' => 1])->once()->andReturnNull(); + + $this->service->handle($this->model, ['config_from' => 1]); + + $this->assertTrue(true); + } + + /** + * Test that an exception is thrown if an invalid config_from attribute is passed. + */ + public function testExceptionIsThrownIfInvalidParentConfigIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $this->model->nest_id], + ['id', '=', 1], + ])->once()->andReturn(0); + + try { + $this->service->handle($this->model, ['config_from' => 1]); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage()); + } + } + + /** + * Test that an integer linking to a model can be passed in place of the Egg model. + */ + public function testIntegerCanBePassedInPlaceOfModel() + { + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->repository->shouldReceive('withoutFreshModel->update') + ->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model->id, ['test_field' => 'field_value']); + + $this->assertTrue(true); + } +} diff --git a/tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php b/tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php new file mode 100644 index 000000000..0bb9e7800 --- /dev/null +++ b/tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php @@ -0,0 +1,113 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException; + +class InstallScriptServiceTest extends TestCase +{ + /** + * @var array + */ + protected $data = [ + 'script_install' => 'test-script', + 'script_is_privileged' => true, + 'script_entry' => '/bin/bash', + 'script_container' => 'ubuntu', + 'copy_script_from' => null, + ]; + + /** + * @var \Pterodactyl\Models\Egg + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Scripts\InstallScriptService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(Egg::class)->make(); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new InstallScriptService($this->repository); + } + + /** + * Test that passing a new copy_script_from attribute works properly. + */ + public function testUpdateWithValidCopyScriptFromAttribute() + { + $this->data['copy_script_from'] = 1; + + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->nest_id)->once()->andReturn(true); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model, $this->data); + } + + /** + * Test that an exception gets raised when the script is not copiable. + */ + public function testUpdateWithInvalidCopyScriptFromAttribute() + { + $this->data['copy_script_from'] = 1; + + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->nest_id)->once()->andReturn(false); + try { + $this->service->handle($this->model, $this->data); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidCopyFromException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.invalid_copy_id'), $exception->getMessage()); + } + } + + /** + * Test standard functionality. + */ + public function testUpdateWithoutNewCopyScriptFromAttribute() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model, $this->data); + } + + /** + * Test that an integer can be passed in place of a model. + */ + public function testFunctionAcceptsIntegerInPlaceOfModel() + { + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model->id, $this->data); + } +} diff --git a/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php new file mode 100644 index 000000000..c40531b97 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Eggs\Sharing; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\EggVariable; +use Tests\Assertions\NestedObjectAssertionsTrait; +use Pterodactyl\Services\Eggs\Sharing\EggExporterService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggExporterServiceTest extends TestCase +{ + use NestedObjectAssertionsTrait; + + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggExporterService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + Carbon::setTestNow(Carbon::now()); + $this->carbon = new Carbon(); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggExporterService($this->repository); + } + + /** + * Test that a JSON structure is returned. + */ + public function testJsonStructureIsExported() + { + $egg = factory(Egg::class)->make(); + $egg->variables = collect([$variable = factory(EggVariable::class)->make()]); + + $this->repository->shouldReceive('getWithExportAttributes')->with($egg->id)->once()->andReturn($egg); + + $response = $this->service->handle($egg->id); + $this->assertNotEmpty($response); + + $data = json_decode($response); + $this->assertEquals(JSON_ERROR_NONE, json_last_error()); + $this->assertObjectHasNestedAttribute('meta.version', $data); + $this->assertObjectNestedValueEquals('meta.version', 'PTDL_v1', $data); + $this->assertObjectHasNestedAttribute('author', $data); + $this->assertObjectNestedValueEquals('author', $egg->author, $data); + $this->assertObjectHasNestedAttribute('exported_at', $data); + $this->assertObjectNestedValueEquals('exported_at', Carbon::now()->toIso8601String(), $data); + $this->assertObjectHasNestedAttribute('scripts.installation.script', $data); + $this->assertObjectHasNestedAttribute('scripts.installation.container', $data); + $this->assertObjectHasNestedAttribute('scripts.installation.entrypoint', $data); + $this->assertObjectHasAttribute('variables', $data); + $this->assertArrayHasKey('0', $data->variables); + $this->assertObjectHasAttribute('name', $data->variables[0]); + $this->assertObjectNestedValueEquals('name', $variable->name, $data->variables[0]); + } +} diff --git a/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php new file mode 100644 index 000000000..98ee03794 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php @@ -0,0 +1,189 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Sharing; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; +use Tests\Traits\MocksUuids; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Models\EggVariable; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +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; + +class EggImporterServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock + */ + protected $eggVariableRepository; + + /** + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock + */ + protected $file; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock + */ + protected $nestRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->eggVariableRepository = m::mock(EggVariableRepositoryInterface::class); + $this->file = m::mock(UploadedFile::class); + $this->nestRepository = m::mock(NestRepositoryInterface::class); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggImporterService( + $this->connection, $this->repository, $this->eggVariableRepository, $this->nestRepository + ); + } + + /** + * Test that a service option can be successfully imported. + */ + public function testEggConfigurationIsImported() + { + $egg = factory(Egg::class)->make(); + $nest = factory(Nest::class)->make(); + + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => $egg->author, + 'variables' => [ + $variable = factory(EggVariable::class)->make(), + ], + ])); + $this->nestRepository->shouldReceive('getWithEggs')->with($nest->id)->once()->andReturn($nest); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with(m::subset([ + 'uuid' => $this->getKnownUuid(), + 'nest_id' => $nest->id, + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->eggVariableRepository->shouldReceive('create')->with(m::subset([ + 'egg_id' => $egg->id, + 'env_variable' => $variable->env_variable, + ]))->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle($this->file, $nest->id); + $this->assertNotEmpty($response); + $this->assertInstanceOf(Egg::class, $response); + $this->assertSame($egg, $response); + } + + /** + * Test that an exception is thrown if the file is invalid. + */ + public function testExceptionIsThrownIfFileIsInvalid() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_NO_FILE); + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the file is not a file. + */ + public function testExceptionIsThrownIfFileIsNotAFile() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the JSON metadata is invalid. + */ + public function testExceptionIsThrownIfJsonMetaDataIsInvalid() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'hodor'], + ])); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if bad JSON is provided. + */ + public function testExceptionIsThrownIfBadJsonIsProvided() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn('}'); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(BadJsonFormatException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ]), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php new file mode 100644 index 000000000..1818058c9 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php @@ -0,0 +1,212 @@ +connection = m::mock(ConnectionInterface::class); + $this->file = m::mock(UploadedFile::class); + $this->repository = m::mock(EggRepositoryInterface::class); + $this->variableRepository = m::mock(EggVariableRepositoryInterface::class); + + $this->service = new EggUpdateImporterService($this->connection, $this->repository, $this->variableRepository); + } + + /** + * Test that an egg update is handled correctly using an uploaded file. + */ + public function testEggIsUpdated() + { + $egg = factory(Egg::class)->make(); + $variable = factory(EggVariable::class)->make(); + + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => 'newauthor@example.com', + 'variables' => [$variable->toArray()], + ])); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with($egg->id, m::subset([ + 'author' => 'newauthor@example.com', + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->variableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->with([ + 'egg_id' => $egg->id, + 'env_variable' => $variable->env_variable, + ], collect($variable)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull(); + + $this->variableRepository->shouldReceive('setColumns')->with(['id', 'env_variable'])->once()->andReturnSelf() + ->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn(collect([$variable])); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($egg->id, $this->file); + $this->assertTrue(true); + } + + /** + * Test that an imported file with less variables than currently existing deletes + * the un-needed variables from the database. + */ + public function testVariablesMissingFromImportAreDeleted() + { + $egg = factory(Egg::class)->make(); + $variable1 = factory(EggVariable::class)->make(); + $variable2 = factory(EggVariable::class)->make(); + + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => 'newauthor@example.com', + 'variables' => [$variable1->toArray()], + ])); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with($egg->id, m::subset([ + 'author' => 'newauthor@example.com', + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->variableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->with([ + 'egg_id' => $egg->id, + 'env_variable' => $variable1->env_variable, + ], collect($variable1)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull(); + + $this->variableRepository->shouldReceive('setColumns')->with(['id', 'env_variable'])->once()->andReturnSelf() + ->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn(collect([$variable1, $variable2])); + + $this->variableRepository->shouldReceive('deleteWhere')->with([ + ['egg_id', '=', $egg->id], + ['env_variable', '=', $variable2->env_variable], + ])->once()->andReturn(1); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($egg->id, $this->file); + $this->assertTrue(true); + } + + /** + * Test that an exception is thrown if the file is invalid. + */ + public function testExceptionIsThrownIfFileIsInvalid() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_NO_FILE); + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the file is not a file. + */ + public function testExceptionIsThrownIfFileIsNotAFile() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the JSON metadata is invalid. + */ + public function testExceptionIsThrownIfJsonMetaDataIsInvalid() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'hodor'], + ])); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if bad JSON is provided. + */ + public function testExceptionIsThrownIfBadJsonIsProvided() + { + $this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn('}'); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(BadJsonFormatException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ]), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php new file mode 100644 index 000000000..a34c5777a --- /dev/null +++ b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php @@ -0,0 +1,127 @@ +repository = m::mock(EggVariableRepositoryInterface::class); + + $this->service = new VariableCreationService($this->repository); + } + + /** + * Test basic functionality, data should be stored in the database. + */ + public function testVariableIsCreatedAndStored() + { + $data = ['env_variable' => 'TEST_VAR_123', 'default_value' => 'test']; + $this->repository->shouldReceive('create')->with(m::subset([ + 'egg_id' => 1, + 'default_value' => 'test', + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ]))->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Test that the option key in the data array is properly parsed. + */ + public function testOptionsPassedInArrayKeyAreParsedProperly() + { + $data = ['env_variable' => 'TEST_VAR_123', 'options' => ['user_viewable', 'user_editable']]; + $this->repository->shouldReceive('create')->with(m::subset([ + 'default_value' => '', + 'user_viewable' => true, + 'user_editable' => true, + 'env_variable' => 'TEST_VAR_123', + ]))->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Test that an empty (null) value passed in the option key is handled + * properly as an array. Also tests the same case aganist the default_value. + * + * @see https://github.com/Pterodactyl/Panel/issues/841 + * @see https://github.com/Pterodactyl/Panel/issues/943 + */ + public function testNullOptionValueIsPassedAsArray() + { + $data = ['env_variable' => 'TEST_VAR_123', 'options' => null, 'default_value' => null]; + $this->repository->shouldReceive('create')->with(m::subset([ + 'default_value' => '', + 'user_viewable' => false, + 'user_editable' => false, + ]))->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Test that all of the reserved variables defined in the model trigger an exception. + * + * @dataProvider reservedNamesProvider + * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable) + { + $this->service->handle(1, ['env_variable' => $variable]); + } + + /** + * Test that the egg ID applied in the function takes higher priority than an + * ID passed into the handler. + */ + public function testEggIdPassedInDataIsNotApplied() + { + $data = ['egg_id' => 123456, 'env_variable' => 'TEST_VAR_123']; + $this->repository->shouldReceive('create')->with(m::subset([ + 'egg_id' => 1, + ]))->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Provides the data to be used in the tests. + * + * @return array + */ + public function reservedNamesProvider() + { + $data = []; + $exploded = explode(',', EggVariable::RESERVED_ENV_NAMES); + foreach ($exploded as $e) { + $data[] = [$e]; + } + + return $data; + } +} diff --git a/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php new file mode 100644 index 000000000..b6d7456d0 --- /dev/null +++ b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php @@ -0,0 +1,183 @@ +model = factory(EggVariable::class)->make(); + $this->repository = m::mock(EggVariableRepositoryInterface::class); + + $this->service = new VariableUpdateService($this->repository); + } + + /** + * Test the function when no env_variable key is passed into the function. + */ + public function testVariableIsUpdatedWhenNoEnvironmentVariableIsPassed() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, m::subset([ + 'user_viewable' => false, + 'user_editable' => false, + ]))->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, [])); + } + + /** + * Test that a null value passed in for the default is converted to a string. + * + * @see https://github.com/Pterodactyl/Panel/issues/934 + */ + public function testNullDefaultValue() + { + $this->repository->shouldReceive('withoutFreshModel->update')->with($this->model->id, m::subset([ + 'user_viewable' => false, + 'user_editable' => false, + 'default_value' => '', + ]))->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, ['default_value' => null])); + } + + /** + * Test the function when a valid env_variable key is passed into the function. + */ + public function testVariableIsUpdatedWhenValidEnvironmentVariableIsPassed() + { + $this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([ + ['env_variable', '=', 'TEST_VAR_123'], + ['egg_id', '=', $this->model->option_id], + ['id', '!=', $this->model->id], + ])->once()->andReturn(0); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, m::subset([ + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ]))->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, ['env_variable' => 'TEST_VAR_123'])); + } + + /** + * Test that an empty (null) value passed in the option key is handled + * properly as an array. Also tests that a null description is handled. + * + * @see https://github.com/Pterodactyl/Panel/issues/841 + */ + public function testNullOptionValueIsPassedAsArray() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, m::subset([ + 'user_viewable' => false, + 'user_editable' => false, + 'description' => '', + ]))->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, ['options' => null, 'description' => null])); + } + + /** + * Test that data passed into the handler is overwritten inside the handler. + */ + public function testDataPassedIntoHandlerTakesLowerPriorityThanDataSet() + { + $this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([ + ['env_variable', '=', 'TEST_VAR_123'], + ['egg_id', '=', $this->model->option_id], + ['id', '!=', $this->model->id], + ])->once()->andReturn(0); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, m::subset([ + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ]))->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, ['user_viewable' => 123456, 'env_variable' => 'TEST_VAR_123'])); + } + + /** + * Test that a non-unique environment variable triggers an exception. + */ + public function testExceptionIsThrownIfEnvironmentVariableIsNotUnique() + { + $this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([ + ['env_variable', '=', 'TEST_VAR_123'], + ['egg_id', '=', $this->model->option_id], + ['id', '!=', $this->model->id], + ])->once()->andReturn(1); + + try { + $this->service->handle($this->model, ['env_variable' => 'TEST_VAR_123']); + } catch (Exception $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.service.variables.env_not_unique', [ + 'name' => 'TEST_VAR_123', + ]), $exception->getMessage()); + } + } + + /** + * Test that all of the reserved variables defined in the model trigger an exception. + * + * @dataProvider reservedNamesProvider + * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable) + { + $this->service->handle($this->model, ['env_variable' => $variable]); + } + + /** + * Provides the data to be used in the tests. + * + * @return array + */ + public function reservedNamesProvider() + { + $data = []; + $exploded = explode(',', EggVariable::RESERVED_ENV_NAMES); + foreach ($exploded as $e) { + $data[] = [$e]; + } + + return $data; + } +} diff --git a/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php b/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php new file mode 100644 index 000000000..57a036599 --- /dev/null +++ b/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php @@ -0,0 +1,168 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Helpers; + +use Closure; +use Mockery as m; +use Tests\TestCase; +use GuzzleHttp\Client; +use Pterodactyl\Services\Helpers\SoftwareVersionService; +use Illuminate\Contracts\Cache\Repository as CacheRepository; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class SoftwareVersionServiceTest extends TestCase +{ + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + protected $cache; + + /** + * @var \GuzzleHttp\Client + */ + protected $client; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var object + */ + protected static $response = [ + 'panel' => '0.2.0', + 'daemon' => '0.1.0', + 'discord' => 'https://pterodactyl.io/discord', + ]; + + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + self::$response = (object) self::$response; + + $this->cache = m::mock(CacheRepository::class); + $this->client = m::mock(Client::class); + $this->config = m::mock(ConfigRepository::class); + + $this->config->shouldReceive('get')->with('pterodactyl.cdn.cache_time')->once()->andReturn(60); + + $this->cache->shouldReceive('remember')->with(SoftwareVersionService::VERSION_CACHE_KEY, 60, Closure::class)->once()->andReturnNull(); + + $this->service = m::mock(SoftwareVersionService::class, [$this->cache, $this->client, $this->config])->makePartial(); + } + + /** + * Test that the panel version is returned. + */ + public function testPanelVersionIsReturned() + { + $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response); + $this->assertEquals(self::$response->panel, $this->service->getPanel()); + } + + /** + * Test that the panel version is returned as error. + */ + public function testPanelVersionIsReturnedAsErrorIfNoKeyIsFound() + { + $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []); + $this->assertEquals('error', $this->service->getPanel()); + } + + /** + * Test that the daemon version is returned. + */ + public function testDaemonVersionIsReturned() + { + $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response); + $this->assertEquals(self::$response->daemon, $this->service->getDaemon()); + } + + /** + * Test that the daemon version is returned as an error. + */ + public function testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound() + { + $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []); + $this->assertEquals('error', $this->service->getDaemon()); + } + + /** + * Test that the discord URL is returned. + */ + public function testDiscordUrlIsReturned() + { + $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response); + $this->assertEquals(self::$response->discord, $this->service->getDiscord()); + } + + /** + * Test that the correct boolean value is returned by the helper for each version passed. + * + * @dataProvider panelVersionProvider + */ + public function testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion($version, $response) + { + $this->config->shouldReceive('get')->with('app.version')->andReturn($version); + $this->service->shouldReceive('getPanel')->withNoArgs()->andReturn(self::$response->panel); + + $this->assertEquals($response, $this->service->isLatestPanel()); + } + + /** + * Test that the correct boolean value is returned. + * + * @dataProvider daemonVersionProvider + */ + public function testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion($version, $response) + { + $this->service->shouldReceive('getDaemon')->withNoArgs()->andReturn(self::$response->daemon); + + $this->assertEquals($response, $this->service->isLatestDaemon($version)); + } + + /** + * Provide data for testing boolean response on panel version. + * + * @return array + */ + public function panelVersionProvider() + { + return [ + [self::$response['panel'], true], + ['0.0.1', false], + ['canary', true], + ]; + } + + /** + * Provide data for testing booklean response for daemon version. + * + * @return array + */ + public function daemonVersionProvider() + { + return [ + [self::$response['daemon'], true], + ['0.0.1', false], + ['0.0.0-canary', true], + ]; + } +} diff --git a/tests/Unit/Services/Helpers/TemporaryPasswordServiceTest.php b/tests/Unit/Services/Helpers/TemporaryPasswordServiceTest.php new file mode 100644 index 000000000..f0bcf253e --- /dev/null +++ b/tests/Unit/Services/Helpers/TemporaryPasswordServiceTest.php @@ -0,0 +1,80 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Helpers; + +use Mockery as m; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Contracts\Config\Repository; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Helpers\TemporaryPasswordService; + +class TemporaryPasswordServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Illuminate\Contracts\Hashing\Hasher|\Mockery\Mock + */ + protected $hasher; + + /** + * @var \Pterodactyl\Services\Helpers\TemporaryPasswordService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->connection = m::mock(ConnectionInterface::class); + $this->hasher = m::mock(Hasher::class); + + $this->service = new TemporaryPasswordService($this->config, $this->connection, $this->hasher); + } + + /** + * Test that a temporary password is stored and the token is returned. + */ + public function testTemporaryPasswordIsStored() + { + $this->getFunctionMock('\\Pterodactyl\\Services\\Helpers', 'str_random') + ->expects($this->once())->with(40)->willReturn('random_string'); + + $this->config->shouldReceive('get')->with('app.key')->once()->andReturn('123456'); + $token = hash_hmac(TemporaryPasswordService::HMAC_ALGO, 'random_string', '123456'); + + $this->hasher->shouldReceive('make')->with($token)->once()->andReturn('hashed_token'); + $this->connection->shouldReceive('table')->with('password_resets')->once()->andReturnSelf(); + $this->connection->shouldReceive('insert')->with([ + 'email' => 'test@example.com', + 'token' => 'hashed_token', + ])->once()->andReturnNull(); + + $response = $this->service->handle('test@example.com'); + $this->assertNotEmpty($response); + $this->assertEquals($token, $response); + } +} diff --git a/tests/Unit/Services/Locations/LocationCreationServiceTest.php b/tests/Unit/Services/Locations/LocationCreationServiceTest.php new file mode 100644 index 000000000..1f0f62b5c --- /dev/null +++ b/tests/Unit/Services/Locations/LocationCreationServiceTest.php @@ -0,0 +1,56 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Locations; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Location; +use Pterodactyl\Services\Locations\LocationCreationService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationCreationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Locations\LocationCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(LocationRepositoryInterface::class); + + $this->service = new LocationCreationService($this->repository); + } + + /** + * Test that a location is created. + */ + public function testLocationIsCreated() + { + $location = factory(Location::class)->make(); + + $this->repository->shouldReceive('create')->with(['test_data' => 'test_value'])->once()->andReturn($location); + + $response = $this->service->handle(['test_data' => 'test_value']); + $this->assertNotEmpty($response); + $this->assertInstanceOf(Location::class, $response); + $this->assertEquals($location, $response); + } +} diff --git a/tests/Unit/Services/Locations/LocationDeletionServiceTest.php b/tests/Unit/Services/Locations/LocationDeletionServiceTest.php new file mode 100644 index 000000000..23d5cb541 --- /dev/null +++ b/tests/Unit/Services/Locations/LocationDeletionServiceTest.php @@ -0,0 +1,76 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Locations; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Locations\LocationDeletionService; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException; + +class LocationDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Locations\LocationDeletionService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->nodeRepository = m::mock(NodeRepositoryInterface::class); + $this->repository = m::mock(LocationRepositoryInterface::class); + + $this->service = new LocationDeletionService($this->repository, $this->nodeRepository); + } + + /** + * Test that a location is deleted. + */ + public function testLocationIsDeleted() + { + $this->nodeRepository->shouldReceive('findCountWhere')->with([['location_id', '=', 123]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(123)->once()->andReturn(1); + + $response = $this->service->handle(123); + $this->assertEquals(1, $response); + } + + /** + * Test that an exception is thrown if nodes are attached to a location. + */ + public function testExceptionIsThrownIfNodesAreAttached() + { + $this->nodeRepository->shouldReceive('findCountWhere')->with([['location_id', '=', 123]])->once()->andReturn(1); + + try { + $this->service->handle(123); + } catch (DisplayException $exception) { + $this->assertInstanceOf(HasActiveNodesException::class, $exception); + $this->assertEquals(trans('exceptions.locations.has_nodes'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Locations/LocationUpdateServiceTest.php b/tests/Unit/Services/Locations/LocationUpdateServiceTest.php new file mode 100644 index 000000000..fc5507d0b --- /dev/null +++ b/tests/Unit/Services/Locations/LocationUpdateServiceTest.php @@ -0,0 +1,67 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Locations; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Location; +use Pterodactyl\Services\Locations\LocationUpdateService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Locations\LocationUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(LocationRepositoryInterface::class); + + $this->service = new LocationUpdateService($this->repository); + } + + /** + * Test location is updated. + */ + public function testLocationIsUpdated() + { + $model = factory(Location::class)->make(['id' => 123]); + $this->repository->shouldReceive('update')->with(123, ['test_data' => 'test_value'])->once()->andReturn($model); + + $response = $this->service->handle($model->id, ['test_data' => 'test_value']); + $this->assertNotEmpty($response); + $this->assertInstanceOf(Location::class, $response); + } + + /** + * Test that a model can be passed in place of an ID. + */ + public function testModelCanBePassedToFunction() + { + $model = factory(Location::class)->make(['id' => 123]); + $this->repository->shouldReceive('update')->with(123, ['test_data' => 'test_value'])->once()->andReturn($model); + + $response = $this->service->handle($model, ['test_data' => 'test_value']); + $this->assertNotEmpty($response); + $this->assertInstanceOf(Location::class, $response); + } +} diff --git a/tests/Unit/Services/Nests/NestCreationServiceTest.php b/tests/Unit/Services/Nests/NestCreationServiceTest.php new file mode 100644 index 000000000..e5d3d7c16 --- /dev/null +++ b/tests/Unit/Services/Nests/NestCreationServiceTest.php @@ -0,0 +1,95 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Nest; +use Tests\Traits\MocksUuids; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; + +class NestCreationServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + private $config; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->repository = m::mock(NestRepositoryInterface::class); + } + + /** + * Test that a new service can be created using the correct data. + */ + public function testCreateNewService() + { + $model = factory(Nest::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('testauthor@example.com'); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'testauthor@example.com', + 'name' => $model->name, + 'description' => $model->description, + ], true, true)->once()->andReturn($model); + + $response = $this->getService()->handle(['name' => $model->name, 'description' => $model->description]); + $this->assertInstanceOf(Nest::class, $response); + $this->assertEquals($model, $response); + } + + /** + * Test creation of a new nest with a defined author. This is used by seeder + * scripts which need to set a specific author for nests in order for other + * functionality to work correctly. + */ + public function testCreateServiceWithDefinedAuthor() + { + $model = factory(Nest::class)->make(); + + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'support@pterodactyl.io', + 'name' => $model->name, + 'description' => $model->description, + ], true, true)->once()->andReturn($model); + + $response = $this->getService()->handle(['name' => $model->name, 'description' => $model->description], 'support@pterodactyl.io'); + $this->assertInstanceOf(Nest::class, $response); + $this->assertEquals($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Nests\NestCreationService + */ + private function getService(): NestCreationService + { + return new NestCreationService($this->config, $this->repository); + } +} diff --git a/tests/Unit/Services/Nests/NestDeletionServiceTest.php b/tests/Unit/Services/Nests/NestDeletionServiceTest.php new file mode 100644 index 000000000..b0bc0b2bb --- /dev/null +++ b/tests/Unit/Services/Nests/NestDeletionServiceTest.php @@ -0,0 +1,92 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class NestDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Nests\NestDeletionService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->repository = m::mock(NestRepositoryInterface::class); + + $this->service = new NestDeletionService($this->serverRepository, $this->repository); + } + + /** + * Test that a service is deleted when there are no servers attached to a service. + */ + public function testServiceIsDeleted() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle(1)); + } + + /** + * Test that an exception is thrown when there are servers attached to a service. + * + * @dataProvider serverCountProvider + * + * @param int $count + */ + public function testExceptionIsThrownIfServersAreAttached(int $count) + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn($count); + + try { + $this->service->handle(1); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(HasActiveServersException::class, $exception); + $this->assertEquals(trans('exceptions.service.delete_has_servers'), $exception->getMessage()); + } + } + + /** + * Provide assorted server counts to ensure that an exception is always thrown when more than 0 servers are found. + * + * @return array + */ + public function serverCountProvider() + { + return [ + [1], [2], [5], [10], + ]; + } +} diff --git a/tests/Unit/Services/Nests/NestUpdateServiceTest.php b/tests/Unit/Services/Nests/NestUpdateServiceTest.php new file mode 100644 index 000000000..0aa2b388f --- /dev/null +++ b/tests/Unit/Services/Nests/NestUpdateServiceTest.php @@ -0,0 +1,62 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; + +class NestUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Nests\NestUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(NestRepositoryInterface::class); + + $this->service = new NestUpdateService($this->repository); + } + + /** + * Test that the author key is removed from the data array before updating the record. + */ + public function testAuthorArrayKeyIsRemovedIfPassed() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with(1, ['otherfield' => 'value'])->once()->andReturnNull(); + + $this->service->handle(1, ['author' => 'author1', 'otherfield' => 'value']); + } + + /** + * Test that the function continues to work when no author key is passed. + */ + public function testServiceIsUpdatedWhenNoAuthorKeyIsPassed() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with(1, ['otherfield' => 'value'])->once()->andReturnNull(); + + $this->service->handle(1, ['otherfield' => 'value']); + } +} diff --git a/tests/Unit/Services/Nodes/NodeCreationServiceTest.php b/tests/Unit/Services/Nodes/NodeCreationServiceTest.php new file mode 100644 index 000000000..d4df2443c --- /dev/null +++ b/tests/Unit/Services/Nodes/NodeCreationServiceTest.php @@ -0,0 +1,59 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Nodes; + +use Mockery as m; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Services\Nodes\NodeCreationService; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; + +class NodeCreationServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Nodes\NodeCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(NodeRepositoryInterface::class); + + $this->service = new NodeCreationService($this->repository); + } + + /** + * Test that a node is created and a daemon secret token is created. + */ + public function testNodeIsCreatedAndDaemonSecretIsGenerated() + { + $this->getFunctionMock('\\Pterodactyl\\Services\\Nodes', 'str_random') + ->expects($this->once())->willReturn('random_string'); + + $this->repository->shouldReceive('create')->with([ + 'name' => 'NodeName', + 'daemonSecret' => 'random_string', + ])->once()->andReturnNull(); + + $this->assertNull($this->service->handle(['name' => 'NodeName'])); + } +} diff --git a/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php new file mode 100644 index 000000000..a7ae2df05 --- /dev/null +++ b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php @@ -0,0 +1,100 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Nodes; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Services\Nodes\NodeDeletionService; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class NodeDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Contracts\Translation\Translator + */ + protected $translator; + + /** + * @var \Pterodactyl\Services\Nodes\NodeDeletionService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(NodeRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->translator = m::mock(Translator::class); + + $this->service = new NodeDeletionService( + $this->repository, + $this->serverRepository, + $this->translator + ); + } + + /** + * Test that a node is deleted if there are no servers attached to it. + */ + public function testNodeIsDeletedIfNoServersAreAttached() + { + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['node_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle(1)); + } + + /** + * Test that an exception is thrown if servers are attached to the node. + * + * @expectedException \Pterodactyl\Exceptions\DisplayException + */ + public function testExceptionIsThrownIfServersAreAttachedToNode() + { + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['node_id', '=', 1]])->once()->andReturn(1); + $this->translator->shouldReceive('trans')->with('exceptions.node.servers_attached')->once()->andReturnNull(); + $this->repository->shouldNotReceive('delete'); + + $this->service->handle(1); + } + + /** + * Test that a model can be passed into the handle function rather than an ID. + */ + public function testModelCanBePassedToFunctionInPlaceOfNodeId() + { + $node = factory(Node::class)->make(); + + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['node_id', '=', $node->id]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with($node->id)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle($node)); + } +} diff --git a/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php new file mode 100644 index 000000000..6dff6e4ba --- /dev/null +++ b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php @@ -0,0 +1,166 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Nodes; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Models\Node; +use GuzzleHttp\Psr7\Response; +use Tests\Traits\MocksRequestException; +use GuzzleHttp\Exception\ConnectException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Nodes\NodeUpdateService; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; + +class NodeUpdateServiceTest extends TestCase +{ + use PHPMock, MocksRequestException; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + private $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface|\Mockery\Mock + */ + private $configRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->configRepository = m::mock(ConfigurationRepositoryInterface::class); + $this->repository = m::mock(NodeRepositoryInterface::class); + } + + /** + * Test that the daemon secret is reset when `reset_secret` is passed in the data. + */ + public function testNodeIsUpdatedAndDaemonSecretIsReset() + { + $model = factory(Node::class)->make(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Nodes', 'str_random') + ->expects($this->once())->willReturn('random_string'); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'name' => 'NewName', + 'daemonSecret' => 'random_string', + ])->andReturn(true); + + $this->configRepository->shouldReceive('setNode')->with($model)->once()->andReturnSelf() + ->shouldReceive('update')->withNoArgs()->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->returnUpdatedModel(false)->handle($model, ['name' => 'NewName', 'reset_secret' => true]); + $this->assertTrue($response); + } + + /** + * Test that daemon secret is not modified when no variable is passed in data. + */ + public function testNodeIsUpdatedAndDaemonSecretIsNotChanged() + { + $model = factory(Node::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'name' => 'NewName', + ])->andReturn(true); + + $this->configRepository->shouldReceive('setNode')->with($model)->once()->andReturnSelf() + ->shouldReceive('update')->withNoArgs()->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->returnUpdatedModel(false)->handle($model, ['name' => 'NewName']); + $this->assertTrue($response); + } + + public function testUpdatedModelIsReturned() + { + $model = factory(Node::class)->make(); + $updated = clone $model; + $updated->name = 'NewName'; + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with($model->id, [ + 'name' => $updated->name, + ])->andReturn($updated); + + $this->configRepository->shouldReceive('setNode')->with($model)->once()->andReturnSelf() + ->shouldReceive('update')->withNoArgs()->once()->andReturn(new Response); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->returnUpdatedModel()->handle($model, ['name' => $updated->name]); + $this->assertInstanceOf(Node::class, $response); + $this->assertSame($updated, $response); + } + + /** + * Test that an exception caused by a connection error is handled. + * + * @expectedException \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException + */ + public function testExceptionRelatedToConnection() + { + $this->configureExceptionMock(ConnectException::class); + $model = factory(Node::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel->update')->andReturn(new Response); + + $this->configRepository->shouldReceive('setNode->update')->once()->andThrow($this->getExceptionMock()); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->getService()->handle($model, ['name' => 'NewName']); + } + + /** + * Test that an exception not caused by a daemon connection error is handled. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionNotRelatedToConnection() + { + $this->configureExceptionMock(); + $model = factory(Node::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel->update')->andReturn(new Response); + + $this->configRepository->shouldReceive('setNode->update')->once()->andThrow($this->getExceptionMock()); + + $this->getService()->handle($model, ['name' => 'NewName']); + } + + /** + * Return an instance of the service with mocked injections. + * + * @return \Pterodactyl\Services\Nodes\NodeUpdateService + */ + private function getService(): NodeUpdateService + { + return new NodeUpdateService($this->connection, $this->configRepository, $this->repository); + } +} diff --git a/tests/Unit/Services/Packs/ExportPackServiceTest.php b/tests/Unit/Services/Packs/ExportPackServiceTest.php new file mode 100644 index 000000000..bcc4db486 --- /dev/null +++ b/tests/Unit/Services/Packs/ExportPackServiceTest.php @@ -0,0 +1,149 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Packs; + +use ZipArchive; +use Mockery as m; +use Tests\TestCase; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Models\Pack; +use Illuminate\Contracts\Filesystem\Factory; +use Pterodactyl\Services\Packs\ExportPackService; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; + +class ExportPackServiceTest extends TestCase +{ + use PHPMock; + + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Packs\ExportPackService + */ + protected $service; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->archive = m::mock(ZipArchive::class); + $this->repository = m::mock(PackRepositoryInterface::class); + $this->storage = m::mock(Factory::class); + + $this->service = new ExportPackService($this->storage, $this->repository, $this->archive); + } + + /** + * Provide standard data to all tests. + */ + protected function setupTestData() + { + $this->model = factory(Pack::class)->make(); + $this->json = [ + 'name' => $this->model->name, + 'version' => $this->model->version, + 'description' => $this->model->description, + 'selectable' => $this->model->selectable, + 'visible' => $this->model->visible, + 'locked' => $this->model->locked, + ]; + } + + /** + * Test that an archive of the entire pack can be exported. + */ + public function testFilesAreBundledIntoZipWhenRequested() + { + $this->setupTestData(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam') + ->expects($this->once())->willReturn('/tmp/myfile.test'); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->never()); + + $this->archive->shouldReceive('open')->with('/tmp/myfile.test', $this->archive::CREATE)->once()->andReturnSelf(); + $this->storage->shouldReceive('disk->files')->with('packs/' . $this->model->uuid)->once()->andReturn(['file_one']); + $this->archive->shouldReceive('addFile')->with(storage_path('app/file_one'), 'file_one')->once()->andReturnSelf(); + $this->archive->shouldReceive('addFromString')->with('import.json', json_encode($this->json, JSON_PRETTY_PRINT))->once()->andReturnSelf(); + $this->archive->shouldReceive('close')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle($this->model, true); + $this->assertEquals('/tmp/myfile.test', $response); + } + + /** + * Test that the pack configuration can be saved as a json file. + */ + public function testPackConfigurationIsSavedAsJsonFile() + { + $this->setupTestData(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam') + ->expects($this->once())->willReturn('/tmp/myfile.test'); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->once())->wilLReturn('fp'); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fwrite') + ->expects($this->once())->with('fp', json_encode($this->json, JSON_PRETTY_PRINT))->willReturn(null); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fclose') + ->expects($this->once())->with('fp')->willReturn(null); + + $response = $this->service->handle($this->model); + $this->assertEquals('/tmp/myfile.test', $response); + } + + /** + * Test that a model ID can be passed in place of the model itself. + */ + public function testPackIdCanBePassedInPlaceOfModel() + { + $this->setupTestData(); + + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')->expects($this->once())->willReturn('/tmp/myfile.test'); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->once())->wilLReturn(null); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fwrite')->expects($this->once())->willReturn(null); + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fclose')->expects($this->once())->willReturn(null); + + $response = $this->service->handle($this->model->id); + $this->assertEquals('/tmp/myfile.test', $response); + } + + /** + * Test that an exception is thrown when a ZipArchive cannot be created. + * + * @expectedException \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException + */ + public function testExceptionIsThrownIfZipArchiveCannotBeCreated() + { + $this->setupTestData(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam') + ->expects($this->once())->willReturn('/tmp/myfile.test'); + + $this->archive->shouldReceive('open')->once()->andReturn(false); + + $this->service->handle($this->model, true); + } +} diff --git a/tests/Unit/Services/Packs/PackCreationServiceTest.php b/tests/Unit/Services/Packs/PackCreationServiceTest.php new file mode 100644 index 000000000..d0baddf30 --- /dev/null +++ b/tests/Unit/Services/Packs/PackCreationServiceTest.php @@ -0,0 +1,182 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Packs; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Pack; +use Tests\Traits\MocksUuids; +use Illuminate\Http\UploadedFile; +use Illuminate\Contracts\Filesystem\Factory; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Packs\PackCreationService; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; + +class PackCreationServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock + */ + protected $file; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $service; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory|\Mockery\Mock + */ + protected $storage; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->file = m::mock(UploadedFile::class); + $this->repository = m::mock(PackRepositoryInterface::class); + $this->storage = m::mock(Factory::class); + + $this->service = new PackCreationService($this->connection, $this->storage, $this->repository); + } + + /** + * Test that a pack is created when no file upload is provided. + */ + public function testPackIsCreatedWhenNoUploadedFileIsPassed() + { + $model = factory(Pack::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'selectable' => false, + 'visible' => false, + 'locked' => false, + 'test-data' => 'value', + ])->once()->andReturn($model); + + $this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle(['test-data' => 'value']); + $this->assertInstanceOf(Pack::class, $response); + $this->assertEquals($model, $response); + } + + /** + * Test that a pack can be created when an uploaded file is provided. + * + * @dataProvider mimetypeProvider + */ + public function testPackIsCreatedWhenUploadedFileIsProvided($mime) + { + $model = factory(Pack::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'selectable' => false, + 'visible' => false, + 'locked' => false, + 'test-data' => 'value', + ])->once()->andReturn($model); + + $this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->file->shouldReceive('storeAs')->with('packs/' . $model->uuid, 'archive.tar.gz')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle(['test-data' => 'value'], $this->file); + $this->assertInstanceOf(Pack::class, $response); + $this->assertEquals($model, $response); + } + + /** + * Test that an exception is thrown if the file upload is not valid. + */ + public function testExceptionIsThrownIfInvalidUploadIsProvided() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle([], $this->file); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown when an invalid mimetype is provided. + * + * @dataProvider invalidMimetypeProvider + */ + public function testExceptionIsThrownIfInvalidMimetypeIsFound($mime) + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime); + + try { + $this->service->handle([], $this->file); + } catch (InvalidFileMimeTypeException $exception) { + $this->assertEquals(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', PackCreationService::VALID_UPLOAD_TYPES), + ]), $exception->getMessage()); + } + } + + /** + * Return an array of valid mimetypes to test aganist. + * + * @return array + */ + public function mimetypeProvider() + { + return [ + ['application/gzip'], + ['application/x-gzip'], + ]; + } + + /** + * Provide invalid mimetypes to test exceptions aganist. + * + * @return array + */ + public function invalidMimetypeProvider() + { + return [ + ['application/zip'], + ['text/plain'], + ['image/jpeg'], + ]; + } +} diff --git a/tests/Unit/Services/Packs/PackDeletionServiceTest.php b/tests/Unit/Services/Packs/PackDeletionServiceTest.php new file mode 100644 index 000000000..75e6684e1 --- /dev/null +++ b/tests/Unit/Services/Packs/PackDeletionServiceTest.php @@ -0,0 +1,121 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Packs; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Pack; +use Illuminate\Contracts\Filesystem\Factory; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Packs\PackDeletionService; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class PackDeletionServiceTest extends TestCase +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Packs\PackDeletionService + */ + protected $service; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->repository = m::mock(PackRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->storage = m::mock(Factory::class); + + $this->service = new PackDeletionService( + $this->connection, + $this->storage, + $this->repository, + $this->serverRepository + ); + } + + /** + * Test that a pack is deleted. + */ + public function testPackIsDeleted() + { + $model = factory(Pack::class)->make(); + + $this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(0); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1); + $this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('deleteDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($model); + } + + /** + * Test that a pack ID can be passed in place of the model. + */ + public function testPackIdCanBePassedInPlaceOfModel() + { + $model = factory(Pack::class)->make(); + + $this->repository->shouldReceive('setColumns')->with(['id', 'uuid'])->once()->andReturnSelf() + ->shouldReceive('find')->with($model->id)->once()->andReturn($model); + $this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(0); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1); + $this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('deleteDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($model->id); + } + + /** + * Test that an exception gets thrown if a server is attached to a pack. + */ + public function testExceptionIsThrownIfServerIsAttachedToPack() + { + $model = factory(Pack::class)->make(); + + $this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(1); + + try { + $this->service->handle($model); + } catch (HasActiveServersException $exception) { + $this->assertEquals(trans('exceptions.packs.delete_has_servers'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Packs/PackUpdateServiceTest.php b/tests/Unit/Services/Packs/PackUpdateServiceTest.php new file mode 100644 index 000000000..614807435 --- /dev/null +++ b/tests/Unit/Services/Packs/PackUpdateServiceTest.php @@ -0,0 +1,99 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Packs; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Pack; +use Pterodactyl\Services\Packs\PackUpdateService; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class PackUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Packs\PackUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(PackRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + + $this->service = new PackUpdateService($this->repository, $this->serverRepository); + } + + /** + * Test that a pack is updated. + */ + public function testPackIsUpdated() + { + $model = factory(Pack::class)->make(); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'locked' => false, + 'visible' => false, + 'selectable' => false, + 'test-data' => 'value', + ])->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle($model, ['test-data' => 'value'])); + } + + /** + * Test that an exception is thrown if the pack option ID is changed while servers are using the pack. + */ + public function testExceptionIsThrownIfModifyingEggIdWhenServersAreAttached() + { + $model = factory(Pack::class)->make(); + $this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(1); + + try { + $this->service->handle($model, ['egg_id' => 0]); + } catch (HasActiveServersException $exception) { + $this->assertEquals(trans('exceptions.packs.update_has_servers'), $exception->getMessage()); + } + } + + /** + * Test that an ID for a pack can be passed in place of the model. + */ + public function testPackIdCanBePassedInPlaceOfModel() + { + $model = factory(Pack::class)->make(); + + $this->repository->shouldReceive('setColumns')->with(['id', 'egg_id'])->once()->andReturnSelf() + ->shouldReceive('find')->with($model->id)->once()->andReturn($model); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'locked' => false, + 'visible' => false, + 'selectable' => false, + 'test-data' => 'value', + ])->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle($model->id, ['test-data' => 'value'])); + } +} diff --git a/tests/Unit/Services/Packs/TemplateUploadServiceTest.php b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php new file mode 100644 index 000000000..c1df4c207 --- /dev/null +++ b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php @@ -0,0 +1,245 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Packs; + +use ZipArchive; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Pack; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Services\Packs\PackCreationService; +use Pterodactyl\Services\Packs\TemplateUploadService; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; +use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; +use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; + +class TemplateUploadServiceTest extends TestCase +{ + const JSON_FILE_CONTENTS = '{"test_content": "value"}'; + + /** + * @var \ZipArchive|\Mockery\Mock + */ + protected $archive; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService|\Mockery\Mock + */ + protected $creationService; + + /** + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock + */ + protected $file; + + /** + * @var \Pterodactyl\Services\Packs\TemplateUploadService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->archive = m::mock(ZipArchive::class); + $this->creationService = m::mock(PackCreationService::class); + $this->file = m::mock(UploadedFile::class); + + $this->service = new TemplateUploadService($this->creationService, $this->archive); + } + + /** + * Test that a JSON file can be processed and turned into a pack. + * + * @dataProvider jsonMimetypeProvider + */ + public function testJsonFileIsProcessed($mime) + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn($mime); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(128); + $this->file->shouldReceive('openFile->fread')->with(128)->once()->andReturn(self::JSON_FILE_CONTENTS); + + $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1]) + ->once()->andReturn(factory(Pack::class)->make()); + + $this->assertInstanceOf(Pack::class, $this->service->handle(1, $this->file)); + } + + /** + * Test that a zip file can be processed. + */ + public function testZipfileIsProcessed() + { + $model = factory(Pack::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip'); + + $this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/real'); + $this->archive->shouldReceive('open')->with('/test/real')->once()->andReturn(true); + $this->archive->shouldReceive('locateName')->with('import.json')->once()->andReturn(true); + $this->archive->shouldReceive('locateName')->with('archive.tar.gz')->once()->andReturn(true); + $this->archive->shouldReceive('getFromName')->with('import.json')->once()->andReturn(self::JSON_FILE_CONTENTS); + $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1]) + ->once()->andReturn($model); + $this->archive->shouldReceive('extractTo')->with(storage_path('app/packs/' . $model->uuid), 'archive.tar.gz') + ->once()->andReturn(true); + $this->archive->shouldReceive('close')->withNoArgs()->once()->andReturnNull(); + + $this->assertInstanceOf(Pack::class, $this->service->handle(1, $this->file)); + } + + /** + * Test that an exception is thrown if the file upload is invalid. + */ + public function testExceptionIsThrownIfFileUploadIsInvalid() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle(1, $this->file); + } catch (InvalidFileUploadException $exception) { + $this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage()); + } + } + + /** + * Test that an invalid mimetype throws an exception. + * + * @dataProvider invalidMimetypeProvider + */ + public function testExceptionIsThrownIfMimetypeIsInvalid($mime) + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime); + + try { + $this->service->handle(1, $this->file); + } catch (InvalidFileMimeTypeException $exception) { + $this->assertEquals(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', TemplateUploadService::VALID_UPLOAD_TYPES), + ]), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the zip is unreadable. + */ + public function testExceptionIsThrownIfZipArchiveIsUnreadable() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip'); + + $this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/path'); + $this->archive->shouldReceive('open')->with('/test/path')->once()->andReturn(false); + + try { + $this->service->handle(1, $this->file); + } catch (UnreadableZipArchiveException $exception) { + $this->assertEquals(trans('exceptions.packs.unreadable'), $exception->getMessage()); + } + } + + /** + * Test that a zip missing the required files throws an exception. + * + * @dataProvider filenameProvider + */ + public function testExceptionIsThrownIfZipDoesNotContainProperFiles($a, $b) + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip'); + + $this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/path'); + $this->archive->shouldReceive('open')->with('/test/path')->once()->andReturn(true); + $this->archive->shouldReceive('locateName')->with('import.json')->once()->andReturn($a); + + if ($a) { + $this->archive->shouldReceive('locateName')->with('archive.tar.gz')->once()->andReturn($b); + } + + try { + $this->service->handle(1, $this->file); + } catch (InvalidPackArchiveFormatException $exception) { + $this->assertEquals(trans('exceptions.packs.invalid_archive_exception'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if an archive cannot be extracted from the zip file. + */ + public function testExceptionIsThrownIfArchiveCannotBeExtractedFromZip() + { + $model = factory(Pack::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip'); + + $this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/real'); + $this->archive->shouldReceive('open')->once()->andReturn(true); + $this->archive->shouldReceive('locateName')->twice()->andReturn(true); + $this->archive->shouldReceive('getFromName')->once()->andReturn(self::JSON_FILE_CONTENTS); + $this->creationService->shouldReceive('handle')->once()->andReturn($model); + $this->archive->shouldReceive('extractTo')->once()->andReturn(false); + + try { + $this->service->handle(1, $this->file); + } catch (ZipExtractionException $exception) { + $this->assertEquals(trans('exceptions.packs.zip_extraction'), $exception->getMessage()); + } + } + + /** + * Provide valid JSON mimetypes to use in tests. + * + * @return array + */ + public function jsonMimetypeProvider() + { + return [ + ['text/plain'], + ['application/json'], + ]; + } + + /** + * Return invalid mimetypes for testing. + * + * @return array + */ + public function invalidMimetypeProvider() + { + return [ + ['application/gzip'], + ['application/x-gzip'], + ['image/jpeg'], + ]; + } + + /** + * Return values for archive->locateName function, import.json and archive.tar.gz respectively. + * + * @return array + */ + public function filenameProvider() + { + return [ + [true, false], + [false, true], + [false, false], + ]; + } +} diff --git a/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php b/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php new file mode 100644 index 000000000..67db3b626 --- /dev/null +++ b/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php @@ -0,0 +1,94 @@ +repository = m::mock(ScheduleRepositoryInterface::class); + $this->runnerService = m::mock(RunTaskService::class); + + $this->service = new ProcessScheduleService($this->runnerService, $this->repository); + } + + /** + * Test that a schedule can be updated and first task set to run. + */ + public function testScheduleIsUpdatedAndRun() + { + $model = factory(Schedule::class)->make(); + $model->setRelation('tasks', collect([$task = factory(Task::class)->make([ + 'sequence_id' => 1, + ])])); + + $this->repository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model); + + $formatted = sprintf('%s %s %s * %s *', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); + $this->repository->shouldReceive('update')->with($model->id, [ + 'is_processing' => true, + 'next_run_at' => CronExpression::factory($formatted)->getNextRunDate(), + ]); + + $this->runnerService->shouldReceive('handle')->with($task)->once()->andReturnNull(); + + $this->service->handle($model); + $this->assertTrue(true); + } + + public function testScheduleRunTimeCanBeOverridden() + { + $model = factory(Schedule::class)->make(); + $model->setRelation('tasks', collect([$task = factory(Task::class)->make([ + 'sequence_id' => 1, + ])])); + + $this->repository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model); + + $this->repository->shouldReceive('update')->with($model->id, [ + 'is_processing' => true, + 'next_run_at' => Carbon::now()->addSeconds(15)->toDateTimeString(), + ]); + + $this->runnerService->shouldReceive('handle')->with($task)->once()->andReturnNull(); + + $this->service->setRunTimeOverride(Carbon::now()->addSeconds(15))->handle($model); + $this->assertTrue(true); + } +} diff --git a/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php b/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php new file mode 100644 index 000000000..62d875fcd --- /dev/null +++ b/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php @@ -0,0 +1,108 @@ +connection = m::mock(ConnectionInterface::class); + $this->repository = m::mock(ScheduleRepositoryInterface::class); + $this->taskCreationService = m::mock(TaskCreationService::class); + + $this->service = new ScheduleCreationService($this->connection, $this->repository, $this->taskCreationService); + } + + /** + * Test that a schedule with no tasks can be created. + */ + public function testScheduleWithNoTasksIsCreated() + { + $schedule = factory(Schedule::class)->make(); + $server = factory(Server::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'server_id' => $server->id, + 'next_run_at' => CronExpression::factory('* * * * * *')->getNextRunDate(), + 'test_key' => 'value', + ])->once()->andReturn($schedule); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle($server, ['test_key' => 'value', 'server_id' => '123abc']); + $this->assertInstanceOf(Schedule::class, $response); + $this->assertEquals($schedule, $response); + } + + /** + * Test that a schedule with at least one task can be created. + */ + public function testScheduleWithTasksIsCreated() + { + $schedule = factory(Schedule::class)->make(); + $server = factory(Server::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'server_id' => $server->id, + 'next_run_at' => CronExpression::factory('* * * * * *')->getNextRunDate(), + 'test_key' => 'value', + ])->once()->andReturn($schedule); + + $this->taskCreationService->shouldReceive('handle')->with($schedule, [ + 'time_interval' => 'm', + 'time_value' => 10, + 'sequence_id' => 1, + 'action' => 'test', + 'payload' => 'testpayload', + ], false)->once()->andReturnNull(); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle($server, ['test_key' => 'value'], [ + ['time_interval' => 'm', 'time_value' => 10, 'action' => 'test', 'payload' => 'testpayload'], + ]); + $this->assertInstanceOf(Schedule::class, $response); + $this->assertEquals($schedule, $response); + } +} diff --git a/tests/Unit/Services/Schedules/ScheduleUpdateServiceTest.php b/tests/Unit/Services/Schedules/ScheduleUpdateServiceTest.php new file mode 100644 index 000000000..3f26f69b9 --- /dev/null +++ b/tests/Unit/Services/Schedules/ScheduleUpdateServiceTest.php @@ -0,0 +1,97 @@ +connection = m::mock(ConnectionInterface::class); + $this->repository = m::mock(ScheduleRepositoryInterface::class); + $this->taskCreationService = m::mock(TaskCreationService::class); + $this->taskRepository = m::mock(TaskRepositoryInterface::class); + } + + /** + * Test that a schedule can be updated. + */ + public function testUpdate() + { + $schedule = factory(Schedule::class)->make(); + $tasks = [['action' => 'test-action']]; + $data = [ + 'cron_minute' => 1, + 'cron_hour' => 2, + 'cron_day_of_month' => 3, + 'cron_day_of_week' => 4, + 'next_run_at' => '_INVALID_VALUE', + ]; + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs(); + $this->repository->shouldReceive('update')->once()->with($schedule->id, array_merge($data, [ + 'next_run_at' => CronExpression::factory('1 2 3 * 4 *')->getNextRunDate(), + ]))->andReturn($schedule); + + $this->taskRepository->shouldReceive('deleteWhere')->once()->with([['schedule_id', '=', $schedule->id]]); + $this->taskCreationService->shouldReceive('handle')->once()->with($schedule, m::subset([ + 'sequence_id' => 1, + 'action' => 'test-action', + ]), false); + + $this->connection->shouldReceive('commit')->once()->withNoArgs(); + + $response = $this->getService()->handle($schedule, $data, $tasks); + $this->assertInstanceOf(Schedule::class, $response); + $this->assertSame($schedule, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Schedules\ScheduleUpdateService + */ + private function getService(): ScheduleUpdateService + { + return new ScheduleUpdateService( + $this->connection, + $this->repository, + $this->taskCreationService, + $this->taskRepository + ); + } +} diff --git a/tests/Unit/Services/Schedules/Tasks/RunTaskServiceTest.php b/tests/Unit/Services/Schedules/Tasks/RunTaskServiceTest.php new file mode 100644 index 000000000..3ca7cc1da --- /dev/null +++ b/tests/Unit/Services/Schedules/Tasks/RunTaskServiceTest.php @@ -0,0 +1,90 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Schedules\Tasks; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Task; +use Illuminate\Support\Facades\Bus; +use Pterodactyl\Jobs\Schedule\RunTaskJob; +use Pterodactyl\Services\Schedules\Tasks\RunTaskService; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; + +class RunTaskServiceTest extends TestCase +{ + /** + * @var \Illuminate\Contracts\Bus\Dispatcher|\Mockery\Mock + */ + protected $dispatcher; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Schedules\Tasks\RunTaskService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + Bus::fake(); + $this->repository = m::mock(TaskRepositoryInterface::class); + + $this->service = new RunTaskService($this->repository); + } + + /** + * Test that a job is dispatched. + */ + public function testTaskIsDispatched() + { + $task = factory(Task::class)->make(); + + $this->repository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once()->andReturnNull(); + + $this->service->handle($task); + + Bus::assertDispatched(RunTaskJob::class, function ($job) use ($task) { + $this->assertEquals($task->id, $job->task, 'Assert job task matches parent task model.'); + $this->assertEquals($task->schedule_id, $job->schedule, 'Assert job is linked to correct schedule.'); + $this->assertEquals($task->time_offset, $job->delay, 'Assert job delay is set correctly to match task.'); + + return true; + }); + } + + /** + * Test that passing an ID in place of a model works. + */ + public function testIdCanBePassedInPlaceOfModel() + { + $task = factory(Task::class)->make(); + + $this->repository->shouldReceive('find')->with($task->id)->once()->andReturn($task); + $this->repository->shouldReceive('update')->with($task->id, ['is_queued' => true])->once()->andReturnNull(); + + $this->service->handle($task->id); + + Bus::assertDispatched(RunTaskJob::class, function ($job) use ($task) { + $this->assertEquals($task->id, $job->task, 'Assert job task matches parent task model.'); + $this->assertEquals($task->schedule_id, $job->schedule, 'Assert job is linked to correct schedule.'); + $this->assertEquals($task->time_offset, $job->delay, 'Assert job delay is set correctly to match task.'); + + return true; + }); + } +} diff --git a/tests/Unit/Services/Schedules/Tasks/TaskCreationServiceTest.php b/tests/Unit/Services/Schedules/Tasks/TaskCreationServiceTest.php new file mode 100644 index 000000000..af517b7eb --- /dev/null +++ b/tests/Unit/Services/Schedules/Tasks/TaskCreationServiceTest.php @@ -0,0 +1,208 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Schedules\Tasks; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Task; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Schedule; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Services\Schedules\Tasks\TaskCreationService; +use Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException; + +class TaskCreationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Schedules\Tasks\TaskCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(TaskRepositoryInterface::class); + + $this->service = new TaskCreationService($this->repository); + } + + /** + * Test that a task is created and a model is returned for the task. + * + * @dataProvider validIntervalProvider + */ + public function testTaskIsCreatedAndModelReturned($interval, $value, $final) + { + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(); + + $this->repository->shouldReceive('create')->with([ + 'schedule_id' => $schedule->id, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->payload, + 'time_offset' => $final, + ], false)->once()->andReturn($task); + + $response = $this->service->handle($schedule, [ + 'time_interval' => $interval, + 'time_value' => $value, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->payload, + ]); + + $this->assertNotEmpty($response); + $this->assertInstanceOf(Task::class, $response); + $this->assertEquals($task, $response); + } + + /** + * Test that no new model is returned when a task is created. + */ + public function testTaskIsCreatedAndModelIsNotReturned() + { + $schedule = factory(Schedule::class)->make(); + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('create')->with([ + 'schedule_id' => $schedule->id, + 'sequence_id' => 1, + 'action' => 'test', + 'payload' => 'testpayload', + 'time_offset' => 300, + ], false)->once()->andReturn(true); + + $response = $this->service->handle($schedule, [ + 'time_interval' => 'm', + 'time_value' => 5, + 'sequence_id' => 1, + 'action' => 'test', + 'payload' => 'testpayload', + ], false); + + $this->assertNotEmpty($response); + $this->assertNotInstanceOf(Task::class, $response); + $this->assertTrue($response); + } + + /** + * Test that an ID can be passed in place of the schedule model itself. + */ + public function testIdCanBePassedInPlaceOfScheduleModel() + { + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('create')->with([ + 'schedule_id' => 1234, + 'sequence_id' => 1, + 'action' => 'test', + 'payload' => 'testpayload', + 'time_offset' => 300, + ], false)->once()->andReturn(true); + + $response = $this->service->handle(1234, [ + 'time_interval' => 'm', + 'time_value' => 5, + 'sequence_id' => 1, + 'action' => 'test', + 'payload' => 'testpayload', + ], false); + + $this->assertNotEmpty($response); + $this->assertNotInstanceOf(Task::class, $response); + $this->assertTrue($response); + } + + /** + * Test exception is thrown if the interval is greater than 15 minutes. + * + * @dataProvider invalidIntervalProvider + */ + public function testExceptionIsThrownIfIntervalIsMoreThan15Minutes($interval, $value) + { + try { + $this->service->handle(1234, [ + 'time_interval' => $interval, + 'time_value' => $value, + ]); + } catch (DisplayException $exception) { + $this->assertInstanceOf(TaskIntervalTooLongException::class, $exception); + $this->assertEquals(trans('exceptions.tasks.chain_interval_too_long'), $exception->getMessage()); + } + } + + /** + * Test that exceptions are thrown if the Scheudle module or ID is invalid. + * + * @dataProvider invalidScheduleArgumentProvider + * @expectedException \InvalidArgumentException + */ + public function testExceptionIsThrownIfInvalidArgumentIsPassed($argument) + { + $this->service->handle($argument, []); + } + + /** + * Provides valid time intervals to be used in tests. + * + * @return array + */ + public function validIntervalProvider() + { + return [ + ['s', 30, 30], + ['s', 60, 60], + ['s', 90, 90], + ['m', 1, 60], + ['m', 5, 300], + ]; + } + + /** + * Return invalid time formats. + * + * @return array + */ + public function invalidIntervalProvider() + { + return [ + ['m', 15.1], + ['m', 16], + ['s', 901], + ]; + } + + /** + * Return an array of invalid schedule data to test aganist. + * + * @return array + */ + public function invalidScheduleArgumentProvider() + { + return [ + [123.456], + ['string'], + ['abc123'], + ['123_test'], + [new Server()], + [Schedule::class], + ]; + } +} diff --git a/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php b/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php new file mode 100644 index 000000000..a16c41225 --- /dev/null +++ b/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php @@ -0,0 +1,72 @@ +exception = m::mock(RequestException::class)->makePartial(); + $this->repository = m::mock(ServerRepositoryInterface::class); + + $this->server = factory(Server::class)->make(['node_id' => 1]); + $this->service = new ContainerRebuildService($this->repository); + } + + /** + * Test that a server is marked for rebuild. + */ + public function testServerIsMarkedForRebuild() + { + $this->repository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() + ->shouldReceive('rebuild')->withNoArgs()->once()->andReturn(new Response); + + $this->service->handle($this->server); + } + + /** + * Test that an exception thrown by guzzle is rendered as a displayable exception. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionThrownByGuzzle() + { + $this->repository->shouldReceive('setServer')->with($this->server)->once()->andThrow($this->exception); + + $this->service->handle($this->server); + } +} diff --git a/tests/Unit/Services/Servers/DetailsModificationServiceTest.php b/tests/Unit/Services/Servers/DetailsModificationServiceTest.php new file mode 100644 index 000000000..bd049d018 --- /dev/null +++ b/tests/Unit/Services/Servers/DetailsModificationServiceTest.php @@ -0,0 +1,130 @@ +connection = m::mock(ConnectionInterface::class); + $this->keyCreationService = m::mock(DaemonKeyCreationService::class); + $this->keyDeletionService = m::mock(DaemonKeyDeletionService::class); + $this->repository = m::mock(ServerRepository::class); + } + + /** + * Test basic updating of core variables when a model is provided. + */ + public function testDetailsAreEdited() + { + $server = factory(Server::class)->make(['owner_id' => 1]); + + $data = ['owner_id' => 1, 'name' => 'New Name', 'description' => 'New Description']; + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('setFreshModel')->once()->with(false)->andReturnSelf(); + $this->repository->shouldReceive('update')->once()->with($server->id, [ + 'owner_id' => $data['owner_id'], + 'name' => $data['name'], + 'description' => $data['description'], + ], true, true)->andReturn(true); + + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $response = $this->getService()->handle($server, $data); + $this->assertTrue($response); + } + + /** + * Test that a model is returned if requested. + */ + public function testModelIsReturned() + { + $server = factory(Server::class)->make(['owner_id' => 1]); + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('setFreshModel')->once()->with(true)->andReturnSelf(); + $this->repository->shouldReceive('update')->once()->andReturn($server); + + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $response = $this->getService()->returnUpdatedModel()->handle($server, ['owner_id' => 1]); + $this->assertInstanceOf(Server::class, $response); + } + + /** + * Test that the daemon secret is reset if the owner id changes. + */ + public function testEditShouldResetDaemonSecretIfOwnerIdIsChanged() + { + $server = factory(Server::class)->make([ + 'owner_id' => 1, + ]); + + $data = ['owner_id' => 2, 'name' => 'New Name', 'description' => 'New Description']; + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('setFreshModel')->once()->with(false)->andReturnSelf(); + $this->repository->shouldReceive('update')->once()->with($server->id, [ + 'owner_id' => $data['owner_id'], + 'name' => $data['name'], + 'description' => $data['description'], + ], true, true)->andReturn(true); + + $this->keyDeletionService->shouldReceive('handle')->once()->with($server, $server->owner_id)->andReturnNull(); + $this->keyCreationService->shouldReceive('handle')->once()->with($server->id, $data['owner_id'])->andReturnNull(); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $response = $this->getService()->handle($server, $data); + $this->assertTrue($response); + } + + /** + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Servers\DetailsModificationService + */ + private function getService(): DetailsModificationService + { + return new DetailsModificationService( + $this->connection, + $this->keyCreationService, + $this->keyDeletionService, + $this->repository + ); + } +} diff --git a/tests/Unit/Services/Servers/EnvironmentServiceTest.php b/tests/Unit/Services/Servers/EnvironmentServiceTest.php new file mode 100644 index 000000000..435eb9bfb --- /dev/null +++ b/tests/Unit/Services/Servers/EnvironmentServiceTest.php @@ -0,0 +1,178 @@ +config = m::mock(Repository::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + } + + /** + * Test that set environment key stores the key into a retreviable array. + */ + public function testSettingEnvironmentKeyPersistsItInArray() + { + $service = $this->getService(); + + $service->setEnvironmentKey('TEST_KEY', function () { + return true; + }); + + $this->assertNotEmpty($service->getEnvironmentKeys()); + $this->assertArrayHasKey('TEST_KEY', $service->getEnvironmentKeys()); + } + + /** + * Test that environment defaults are returned by the process function. + */ + public function testProcessShouldReturnDefaultEnvironmentVariablesForAServer() + { + $model = $this->getServerModel(); + $this->config->shouldReceive('get')->with(self::CONFIG_MAPPING, [])->once()->andReturn([]); + $this->repository->shouldReceive('getVariablesWithValues')->with($model->id)->once()->andReturn([ + 'TEST_VARIABLE' => 'Test Variable', + ]); + + $response = $this->getService()->handle($model); + $this->assertNotEmpty($response); + $this->assertEquals(4, count($response)); + $this->assertArrayHasKey('TEST_VARIABLE', $response); + $this->assertSame('Test Variable', $response['TEST_VARIABLE']); + } + + /** + * Test that variables included at run-time are also included. + */ + public function testProcessShouldReturnKeySetAtRuntime() + { + $model = $this->getServerModel(); + $this->config->shouldReceive('get')->with(self::CONFIG_MAPPING, [])->once()->andReturn([]); + $this->repository->shouldReceive('getVariablesWithValues')->with($model->id)->once()->andReturn([]); + + $service = $this->getService(); + $service->setEnvironmentKey('TEST_VARIABLE', function ($server) { + return $server->uuidShort; + }); + + $response = $service->handle($model); + + $this->assertNotEmpty($response); + $this->assertArrayHasKey('TEST_VARIABLE', $response); + $this->assertSame($model->uuidShort, $response['TEST_VARIABLE']); + } + + /** + * Test that duplicate variables provided in config override the defaults. + */ + public function testProcessShouldAllowOverwritingVaraiblesWithConfigurationFile() + { + $model = $this->getServerModel(); + $this->repository->shouldReceive('getVariablesWithValues')->with($model->id)->once()->andReturn([]); + $this->config->shouldReceive('get')->with(self::CONFIG_MAPPING, [])->once()->andReturn([ + 'P_SERVER_UUID' => 'name', + ]); + + $response = $this->getService()->handle($model); + + $this->assertNotEmpty($response); + $this->assertSame(3, count($response)); + $this->assertArrayHasKey('P_SERVER_UUID', $response); + $this->assertSame($model->name, $response['P_SERVER_UUID']); + } + + /** + * Test that config based environment variables can be done using closures. + */ + public function testVariablesSetInConfigurationAllowForClosures() + { + $model = $this->getServerModel(); + $this->config->shouldReceive('get')->with(self::CONFIG_MAPPING, [])->once()->andReturn([ + 'P_SERVER_UUID' => function ($server) { + return $server->id * 2; + }, + ]); + $this->repository->shouldReceive('getVariablesWithValues')->with($model->id)->once()->andReturn([]); + + $response = $this->getService()->handle($model); + + $this->assertNotEmpty($response); + $this->assertSame(3, count($response)); + $this->assertArrayHasKey('P_SERVER_UUID', $response); + $this->assertSame($model->id * 2, $response['P_SERVER_UUID']); + } + + /** + * Test that duplicate variables provided at run-time override the defaults and those + * that are defined in the configuration file. + */ + public function testProcessShouldAllowOverwritingDefaultVariablesWithRuntimeProvided() + { + $model = $this->getServerModel(); + $this->config->shouldReceive('get')->with(self::CONFIG_MAPPING, [])->once()->andReturn([ + 'P_SERVER_UUID' => 'overwritten-config', + ]); + $this->repository->shouldReceive('getVariablesWithValues')->with($model->id)->once()->andReturn([]); + + $service = $this->getService(); + $service->setEnvironmentKey('P_SERVER_UUID', function ($model) { + return 'overwritten'; + }); + + $response = $service->handle($model); + + $this->assertNotEmpty($response); + $this->assertSame(3, count($response)); + $this->assertArrayHasKey('P_SERVER_UUID', $response); + $this->assertSame('overwritten', $response['P_SERVER_UUID']); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\EnvironmentService + */ + private function getService(): EnvironmentService + { + return new EnvironmentService($this->config, $this->repository); + } + + /** + * Return a server model with a location relationship to be used in the tests. + * + * @return \Pterodactyl\Models\Server + */ + private function getServerModel(): Server + { + return factory(Server::class)->make([ + 'location' => factory(Location::class)->make(), + ]); + } +} diff --git a/tests/Unit/Services/Servers/ReinstallServerServiceTest.php b/tests/Unit/Services/Servers/ReinstallServerServiceTest.php new file mode 100644 index 000000000..349aa571a --- /dev/null +++ b/tests/Unit/Services/Servers/ReinstallServerServiceTest.php @@ -0,0 +1,151 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Servers; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Servers\ReinstallServerService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ReinstallServerServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \GuzzleHttp\Exception\RequestException + */ + protected $exception; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Models\Server + */ + protected $server; + + /** + * @var \Pterodactyl\Services\Servers\ReinstallServerService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->database = m::mock(ConnectionInterface::class); + $this->exception = m::mock(RequestException::class)->makePartial(); + $this->repository = m::mock(ServerRepositoryInterface::class); + + $this->server = factory(Server::class)->make(['node_id' => 1]); + + $this->service = new ReinstallServerService( + $this->database, + $this->daemonServerRepository, + $this->repository + ); + } + + /** + * Test that a server is reinstalled when it's model is passed to the function. + */ + public function testServerShouldBeReinstalledWhenModelIsPassed() + { + $this->repository->shouldNotReceive('find'); + + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, [ + 'installed' => 0, + ])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() + ->shouldReceive('reinstall')->withNoArgs()->once()->andReturn(new Response); + $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->reinstall($this->server); + } + + /** + * Test that a server is reinstalled when the ID of the server is passed to the function. + */ + public function testServerShouldBeReinstalledWhenServerIdIsPassed() + { + $this->repository->shouldReceive('find')->with($this->server->id)->once()->andReturn($this->server); + + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, [ + 'installed' => 0, + ])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() + ->shouldReceive('reinstall')->withNoArgs()->once()->andReturn(new Response); + $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->reinstall($this->server->id); + } + + /** + * Test that an exception thrown by guzzle is rendered as a displayable exception. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable() + { + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, [ + 'installed' => 0, + ])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andThrow($this->exception); + + $this->service->reinstall($this->server); + } + + /** + * Test that an exception thrown by something other than guzzle is not transformed to a displayable. + * + * @expectedException \Exception + */ + public function testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable() + { + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, [ + 'installed' => 0, + ])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andThrow(new Exception()); + + $this->service->reinstall($this->server); + } +} diff --git a/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php new file mode 100644 index 000000000..75de1c061 --- /dev/null +++ b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php @@ -0,0 +1,101 @@ +environment = m::mock(EnvironmentService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + } + + /** + * Test that a configuration is returned in the proper format when passed a + * server model that is missing required relationships. + */ + public function testCorrectStructureIsReturned() + { + $model = factory(Server::class)->make(); + $model->setRelation('pack', null); + $model->setRelation('allocation', factory(Allocation::class)->make()); + $model->setRelation('allocations', collect(factory(Allocation::class)->times(2)->make())); + $model->setRelation('egg', factory(Egg::class)->make()); + + $portListing = $model->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(); + + $this->repository->shouldReceive('getDataForCreation')->with($model)->once()->andReturn($model); + $this->environment->shouldReceive('handle')->with($model)->once()->andReturn(['environment_array']); + + $response = $this->getService()->handle($model); + $this->assertNotEmpty($response); + $this->assertArrayNotHasKey('user', $response); + $this->assertArrayNotHasKey('keys', $response); + $this->assertArrayHasKey('uuid', $response); + $this->assertArrayHasKey('build', $response); + $this->assertArrayHasKey('service', $response); + $this->assertArrayHasKey('rebuild', $response); + $this->assertArrayHasKey('suspended', $response); + + $this->assertArraySubset([ + 'default' => [ + 'ip' => $model->allocation->ip, + 'port' => $model->allocation->port, + ], + ], $response['build'], true, 'Assert server default allocation is correct.'); + $this->assertArraySubset(['ports' => $portListing], $response['build'], true, 'Assert server ports are correct.'); + $this->assertArraySubset([ + 'env' => ['environment_array'], + 'swap' => (int) $model->swap, + 'io' => (int) $model->io, + 'cpu' => (int) $model->cpu, + 'disk' => (int) $model->disk, + 'image' => $model->image, + ], $response['build'], true, 'Assert server build data is correct.'); + + $this->assertArraySubset([ + 'egg' => $model->egg->uuid, + 'pack' => null, + 'skip_scripts' => $model->skip_scripts, + ], $response['service']); + + $this->assertFalse($response['rebuild']); + $this->assertSame((int) $model->suspended, $response['suspended']); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + private function getService(): ServerConfigurationStructureService + { + return new ServerConfigurationStructureService($this->repository, $this->environment); + } +} diff --git a/tests/Unit/Services/Servers/ServerCreationServiceTest.php b/tests/Unit/Services/Servers/ServerCreationServiceTest.php new file mode 100644 index 000000000..309508dbf --- /dev/null +++ b/tests/Unit/Services/Servers/ServerCreationServiceTest.php @@ -0,0 +1,279 @@ +allocationRepository = m::mock(AllocationRepositoryInterface::class); + $this->allocationSelectionService = m::mock(AllocationSelectionService::class); + $this->configurationStructureService = m::mock(ServerConfigurationStructureService::class); + $this->connection = m::mock(ConnectionInterface::class); + $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->eggRepository = m::mock(EggRepositoryInterface::class); + $this->findViableNodesService = m::mock(FindViableNodesService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class); + $this->userRepository = m::mock(UserRepositoryInterface::class); + $this->validatorService = m::mock(VariableValidatorService::class); + } + + /** + * Test core functionality of the creation process. + */ + public function testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer() + { + $model = factory(Server::class)->make([ + 'uuid' => $this->getKnownUuid(), + ]); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with(m::subset([ + 'uuid' => $this->getKnownUuid(), + 'node_id' => $model->node_id, + 'allocation_id' => $model->allocation_id, + 'owner_id' => $model->owner_id, + 'nest_id' => $model->nest_id, + 'egg_id' => $model->egg_id, + ]))->once()->andReturn($model); + + $this->allocationRepository->shouldReceive('assignAllocationsToServer')->with($model->id, [$model->allocation_id])->once()->andReturn(1); + + $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_ADMIN)->once()->andReturnSelf(); + $this->validatorService->shouldReceive('handle')->with($model->egg_id, [])->once()->andReturn( + collect([(object) ['id' => 123, 'value' => 'var1-value']]) + ); + + $this->serverVariableRepository->shouldReceive('insert')->with([ + [ + 'server_id' => $model->id, + 'variable_id' => 123, + 'variable_value' => 'var1-value', + ], + ])->once()->andReturn(true); + $this->configurationStructureService->shouldReceive('handle')->with($model)->once()->andReturn(['test' => 'struct']); + + $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); + $this->daemonServerRepository->shouldReceive('create')->with(['test' => 'struct'], ['start_on_completion' => false])->once(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($model->toArray()); + + $this->assertSame($model, $response); + } + + /** + * Test that optional parameters get auto-filled correctly on the model. + */ + public function testDataIsAutoFilled() + { + $model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]); + $allocationModel = factory(Allocation::class)->make(['node_id' => $model->node_id]); + $eggModel = factory(Egg::class)->make(['nest_id' => $model->nest_id]); + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs(); + $this->allocationRepository->shouldReceive('setColumns->find')->once()->with($model->allocation_id)->andReturn($allocationModel); + $this->eggRepository->shouldReceive('setColumns->find')->once()->with($model->egg_id)->andReturn($eggModel); + + $this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([])); + $this->repository->shouldReceive('create')->once()->with(m::subset([ + 'uuid' => $this->getKnownUuid(), + 'node_id' => $model->node_id, + 'allocation_id' => $model->allocation_id, + 'nest_id' => $model->nest_id, + 'egg_id' => $model->egg_id, + ]))->andReturn($model); + + $this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]); + $this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]); + + $this->daemonServerRepository->shouldReceive('setServer->create')->once(); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $this->getService()->handle( + collect($model->toArray())->except(['node_id', 'nest_id'])->toArray() + ); + } + + /** + * Test that an auto-deployment object is used correctly if passed. + */ + public function testAutoDeploymentObject() + { + $model = factory(Server::class)->make(['uuid' => $this->getKnownUuid()]); + $deploymentObject = new DeploymentObject(); + $deploymentObject->setPorts(['25565']); + $deploymentObject->setDedicated(false); + $deploymentObject->setLocations([1]); + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs(); + + $this->findViableNodesService->shouldReceive('setLocations')->once()->with($deploymentObject->getLocations())->andReturnSelf(); + $this->findViableNodesService->shouldReceive('setDisk')->once()->with($model->disk)->andReturnSelf(); + $this->findViableNodesService->shouldReceive('setMemory')->once()->with($model->memory)->andReturnSelf(); + $this->findViableNodesService->shouldReceive('handle')->once()->withNoArgs()->andReturn([1, 2]); + + $allocationModel = factory(Allocation::class)->make([ + 'id' => $model->allocation_id, + 'node_id' => $model->node_id, + ]); + $this->allocationSelectionService->shouldReceive('setDedicated')->once()->with($deploymentObject->isDedicated())->andReturnSelf(); + $this->allocationSelectionService->shouldReceive('setNodes')->once()->with([1, 2])->andReturnSelf(); + $this->allocationSelectionService->shouldReceive('setPorts')->once()->with($deploymentObject->getPorts())->andReturnSelf(); + $this->allocationSelectionService->shouldReceive('handle')->once()->withNoArgs()->andReturn($allocationModel); + + $this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([])); + $this->repository->shouldReceive('create')->once()->with(m::subset([ + 'uuid' => $this->getKnownUuid(), + 'node_id' => $model->node_id, + 'allocation_id' => $model->allocation_id, + 'nest_id' => $model->nest_id, + 'egg_id' => $model->egg_id, + ]))->andReturn($model); + + $this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->with($model->id, [$model->allocation_id]); + $this->configurationStructureService->shouldReceive('handle')->once()->with($model)->andReturn([]); + + $this->daemonServerRepository->shouldReceive('setServer->create')->once(); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $this->getService()->handle( + collect($model->toArray())->except(['allocation_id', 'node_id'])->toArray(), $deploymentObject + ); + } + + /** + * Test handling of node timeout or other daemon error. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionShouldBeThrownIfTheRequestFails() + { + $this->configureExceptionMock(); + + $model = factory(Server::class)->make([ + 'uuid' => $this->getKnownUuid(), + ]); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->once()->andReturn($model); + $this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->andReturn(1); + $this->validatorService->shouldReceive('setUserLevel')->once()->andReturnSelf(); + $this->validatorService->shouldReceive('handle')->once()->andReturn(collect([])); + $this->configurationStructureService->shouldReceive('handle')->once()->andReturn([]); + + $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andThrow($this->getExceptionMock()); + $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); + + $this->getService()->handle($model->toArray()); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\ServerCreationService + */ + private function getService(): ServerCreationService + { + return new ServerCreationService( + $this->allocationRepository, + $this->allocationSelectionService, + $this->connection, + $this->daemonServerRepository, + $this->eggRepository, + $this->findViableNodesService, + $this->configurationStructureService, + $this->repository, + $this->serverVariableRepository, + $this->validatorService + ); + } +} diff --git a/tests/Unit/Services/Servers/ServerDeletionServiceTest.php b/tests/Unit/Services/Servers/ServerDeletionServiceTest.php new file mode 100644 index 000000000..d93d2e985 --- /dev/null +++ b/tests/Unit/Services/Servers/ServerDeletionServiceTest.php @@ -0,0 +1,156 @@ +connection = m::mock(ConnectionInterface::class); + $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->databaseRepository = m::mock(DatabaseRepositoryInterface::class); + $this->databaseManagementService = m::mock(DatabaseManagementService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->writer = m::mock(Writer::class); + } + + /** + * Test that a server can be force deleted by setting it in a function call. + */ + public function testForceParameterCanBeSet() + { + $response = $this->getService()->withForce(true); + + $this->assertInstanceOf(ServerDeletionService::class, $response); + } + + /** + * Test that a server can be deleted when force is not set. + */ + public function testServerCanBeDeletedWithoutForce() + { + $model = factory(Server::class)->make(); + + $this->daemonServerRepository->shouldReceive('setServer')->once()->with($model)->andReturnSelf(); + $this->daemonServerRepository->shouldReceive('delete')->once()->withNoArgs()->andReturn(new Response); + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->databaseRepository->shouldReceive('setColumns')->once()->with('id')->andReturnSelf(); + $this->databaseRepository->shouldReceive('findWhere')->once()->with([ + ['server_id', '=', $model->id], + ])->andReturn(collect([(object) ['id' => 50]])); + + $this->databaseManagementService->shouldReceive('delete')->once()->with(50)->andReturnNull(); + $this->repository->shouldReceive('delete')->once()->with($model->id)->andReturn(1); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); + + $this->getService()->handle($model); + } + + /** + * Test that a server is deleted when force is set. + */ + public function testServerShouldBeDeletedEvenWhenFailureOccursIfForceIsSet() + { + $this->configureExceptionMock(); + $model = factory(Server::class)->make(); + + $this->daemonServerRepository->shouldReceive('setServer')->once()->with($model)->andReturnSelf(); + $this->daemonServerRepository->shouldReceive('delete')->once()->withNoArgs()->andThrow($this->getExceptionMock()); + + $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->databaseRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf(); + $this->databaseRepository->shouldReceive('findWhere')->with([ + ['server_id', '=', $model->id], + ])->once()->andReturn(collect([(object) ['id' => 50]])); + + $this->databaseManagementService->shouldReceive('delete')->with(50)->once()->andReturnNull(); + $this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->getService()->withForce()->handle($model); + } + + /** + * Test that an exception is thrown if a server cannot be deleted from the node and force is not set. + * + * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function testExceptionShouldBeThrownIfDaemonReturnsAnErrorAndForceIsNotSet() + { + $this->configureExceptionMock(); + $model = factory(Server::class)->make(); + + $this->daemonServerRepository->shouldReceive('setServer->delete')->once()->andThrow($this->getExceptionMock()); + + $this->getService()->handle($model); + } + + /** + * Return an instance of the class with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\ServerDeletionService + */ + private function getService(): ServerDeletionService + { + return new ServerDeletionService( + $this->connection, + $this->daemonServerRepository, + $this->databaseRepository, + $this->databaseManagementService, + $this->repository, + $this->writer + ); + } +} diff --git a/tests/Unit/Services/Servers/StartupCommandViewServiceTest.php b/tests/Unit/Services/Servers/StartupCommandViewServiceTest.php new file mode 100644 index 000000000..12f71f134 --- /dev/null +++ b/tests/Unit/Services/Servers/StartupCommandViewServiceTest.php @@ -0,0 +1,85 @@ +repository = m::mock(ServerRepositoryInterface::class); + } + + /** + * Test that the correct startup string is returned. + */ + public function testServiceResponse() + { + $allocation = factory(Allocation::class)->make(); + $egg = factory(Egg::class)->make(); + $server = factory(Server::class)->make([ + 'startup' => 'example {{SERVER_MEMORY}} {{SERVER_IP}} {{SERVER_PORT}} {{TEST_VARIABLE}} {{TEST_VARIABLE_HIDDEN}} {{UNKNOWN}}', + ]); + + $variables = collect([ + factory(EggVariable::class)->make(['env_variable' => 'TEST_VARIABLE', 'user_viewable' => 1]), + factory(EggVariable::class)->make(['env_variable' => 'TEST_VARIABLE_HIDDEN', 'user_viewable' => 0]), + ]); + + $egg->setRelation('variables', $variables); + $server->setRelation('allocation', $allocation); + $server->setRelation('egg', $egg); + + $this->repository->shouldReceive('getVariablesWithValues')->once()->with($server->id, true)->andReturn((object) [ + 'data' => [ + 'TEST_VARIABLE' => 'Test Value', + 'TEST_VARIABLE_HIDDEN' => 'Hidden Value', + ], + 'server' => $server, + ]); + + $this->repository->shouldReceive('getPrimaryAllocation')->once()->with($server)->andReturn($server); + + $response = $this->getService()->handle($server->id); + $this->assertInstanceOf(Collection::class, $response); + + $this->assertSame( + sprintf('example %s %s %s %s %s {{UNKNOWN}}', $server->memory, $allocation->ip, $allocation->port, 'Test Value', '[hidden]'), + $response->get('startup') + ); + $this->assertEquals($variables->only(0), $response->get('variables')); + $this->assertSame([ + 'TEST_VARIABLE' => 'Test Value', + 'TEST_VARIABLE_HIDDEN' => 'Hidden Value', + ], $response->get('server_values')); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\StartupCommandViewService + */ + private function getService(): StartupCommandViewService + { + return new StartupCommandViewService($this->repository); + } +} diff --git a/tests/Unit/Services/Servers/StartupModificationServiceTest.php b/tests/Unit/Services/Servers/StartupModificationServiceTest.php new file mode 100644 index 000000000..99453e515 --- /dev/null +++ b/tests/Unit/Services/Servers/StartupModificationServiceTest.php @@ -0,0 +1,194 @@ +daemonServerRepository = m::mock(DaemonServerRepository::class); + $this->connection = m::mock(ConnectionInterface::class); + $this->eggRepository = m::mock(EggRepositoryInterface::class); + $this->environmentService = m::mock(EnvironmentService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class); + $this->validatorService = m::mock(VariableValidatorService::class); + } + + /** + * Test startup modification as a non-admin user. + */ + public function testStartupModifiedAsNormalUser() + { + $model = factory(Server::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull(); + $this->validatorService->shouldReceive('handle')->with(123, ['test' => 'abcd1234'])->once()->andReturn( + collect([(object) ['id' => 1, 'value' => 'stored-value']]) + ); + + $this->serverVariableRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->serverVariableRepository->shouldReceive('updateOrCreate')->with([ + 'server_id' => $model->id, + 'variable_id' => 1, + ], ['variable_value' => 'stored-value'])->once()->andReturnNull(); + + $this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']); + $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); + $this->daemonServerRepository->shouldReceive('update')->with([ + 'build' => ['env|overwrite' => ['env']], + ])->once()->andReturn(new Response); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($model, ['egg_id' => 123, 'environment' => ['test' => 'abcd1234']]); + + $this->assertInstanceOf(Server::class, $response); + $this->assertSame($model, $response); + } + + /** + * Test startup modification as an admin user. + */ + public function testStartupModificationAsAdminUser() + { + $model = factory(Server::class)->make([ + 'egg_id' => 123, + 'image' => 'docker:image', + ]); + + $eggModel = factory(Egg::class)->make([ + 'id' => 456, + 'nest_id' => 12345, + ]); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_ADMIN)->once()->andReturnNull(); + $this->validatorService->shouldReceive('handle')->with(456, ['test' => 'abcd1234'])->once()->andReturn( + collect([(object) ['id' => 1, 'value' => 'stored-value']]) + ); + + $this->serverVariableRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); + $this->serverVariableRepository->shouldReceive('updateOrCreate')->with([ + 'server_id' => $model->id, + 'variable_id' => 1, + ], ['variable_value' => 'stored-value'])->once()->andReturnNull(); + + $this->eggRepository->shouldReceive('setColumns->find')->once()->with($eggModel->id)->andReturn($eggModel); + + $this->repository->shouldReceive('update')->with($model->id, m::subset([ + 'installed' => 0, + 'nest_id' => $eggModel->nest_id, + 'egg_id' => $eggModel->id, + 'pack_id' => 789, + 'image' => 'docker:image', + ]))->once()->andReturn($model); + $this->repository->shouldReceive('getDaemonServiceData')->with($model, true)->once()->andReturn([ + 'egg' => 'abcd1234', + 'pack' => 'xyz987', + ]); + + $this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']); + + $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); + $this->daemonServerRepository->shouldReceive('update')->with([ + 'build' => [ + 'env|overwrite' => ['env'], + 'image' => $model->image, + ], + 'service' => [ + 'egg' => 'abcd1234', + 'pack' => 'xyz987', + 'skip_scripts' => false, + ], + ])->once()->andReturn(new Response); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $service = $this->getService(); + $service->setUserLevel(User::USER_LEVEL_ADMIN); + $response = $service->handle($model, [ + 'docker_image' => 'docker:image', + 'egg_id' => $eggModel->id, + 'pack_id' => 789, + 'environment' => ['test' => 'abcd1234'], + ]); + + $this->assertInstanceOf(Server::class, $response); + $this->assertSame($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\StartupModificationService + */ + private function getService(): StartupModificationService + { + return new StartupModificationService( + $this->connection, + $this->daemonServerRepository, + $this->eggRepository, + $this->environmentService, + $this->repository, + $this->serverVariableRepository, + $this->validatorService + ); + } +} diff --git a/tests/Unit/Services/Servers/SuspensionServiceTest.php b/tests/Unit/Services/Servers/SuspensionServiceTest.php new file mode 100644 index 000000000..116014277 --- /dev/null +++ b/tests/Unit/Services/Servers/SuspensionServiceTest.php @@ -0,0 +1,192 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Servers; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Illuminate\Log\Writer; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Servers\SuspensionService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SuspensionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \GuzzleHttp\Exception\RequestException + */ + protected $exception; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Models\Server + */ + protected $server; + + /** + * @var \Pterodactyl\Services\Servers\SuspensionService + */ + protected $service; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->database = m::mock(ConnectionInterface::class); + $this->exception = m::mock(RequestException::class)->makePartial(); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->writer = m::mock(Writer::class); + + $this->server = factory(Server::class)->make(['suspended' => 0, 'node_id' => 1]); + + $this->service = new SuspensionService( + $this->database, + $this->daemonServerRepository, + $this->repository, + $this->writer + ); + } + + /** + * Test that the function accepts an integer in place of the server model. + * + * @expectedException \Exception + */ + public function testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel() + { + $this->repository->shouldReceive('find')->with($this->server->id)->once()->andThrow(new Exception()); + + $this->service->toggle($this->server->id); + } + + /** + * Test that no action being passed suspends a server. + */ + public function testServerShouldBeSuspendedWhenNoActionIsPassed() + { + $this->server->suspended = 0; + + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, ['suspended' => true])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() + ->shouldReceive('suspend')->withNoArgs()->once()->andReturn(new Response); + $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->assertTrue($this->service->toggle($this->server)); + } + + /** + * Test that server is unsuspended if action=unsuspend. + */ + public function testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed() + { + $this->server->suspended = 1; + + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, ['suspended' => false])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() + ->shouldReceive('unsuspend')->withNoArgs()->once()->andReturn(new Response); + $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->assertTrue($this->service->toggle($this->server, 'unsuspend')); + } + + /** + * Test that nothing happens if a server is already unsuspended and action=unsuspend. + */ + public function testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend() + { + $this->server->suspended = 0; + + $this->assertTrue($this->service->toggle($this->server, 'unsuspend')); + } + + /** + * Test that nothing happens if a server is already suspended and action=suspend. + */ + public function testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend() + { + $this->server->suspended = 1; + + $this->assertTrue($this->service->toggle($this->server, 'suspend')); + } + + /** + * Test that an exception thrown by Guzzle is caught and transformed to a displayable exception. + */ + public function testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable() + { + $this->server->suspended = 0; + + $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->server->id, ['suspended' => true])->once()->andReturnNull(); + + $this->daemonServerRepository->shouldReceive('setServer')->with($this->server) + ->once()->andThrow($this->exception); + + $this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('getStatusCode')->withNoArgs()->once()->andReturn(400); + + $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); + + try { + $this->service->toggle($this->server); + } catch (Exception $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals( + trans('admin/server.exceptions.daemon_exception', ['code' => 400]), + $exception->getMessage() + ); + } + } + + /** + * Test that if action is not suspend or unsuspend an exception is thrown. + * + * @expectedException \InvalidArgumentException + */ + public function testExceptionShouldBeThrownIfActionIsNotValid() + { + $this->service->toggle($this->server, 'random'); + } +} diff --git a/tests/Unit/Services/Servers/VariableValidatorServiceTest.php b/tests/Unit/Services/Servers/VariableValidatorServiceTest.php new file mode 100644 index 000000000..0ce14eec2 --- /dev/null +++ b/tests/Unit/Services/Servers/VariableValidatorServiceTest.php @@ -0,0 +1,175 @@ +optionVariableRepository = m::mock(EggVariableRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class); + } + + /** + * Test that when no variables are found for an option no data is returned. + */ + public function testEmptyResultSetShouldBeReturnedIfNoVariablesAreFound() + { + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn(collect([])); + + $response = $this->getService()->handle(1, []); + $this->assertEmpty($response); + $this->assertInstanceOf(Collection::class, $response); + } + + /** + * Test that variables set as user_editable=0 and/or user_viewable=0 are skipped when admin flag is not set. + */ + public function testValidatorShouldNotProcessVariablesSetAsNotUserEditableWhenAdminFlagIsNotPassed() + { + $variables = $this->getVariableCollection(); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables); + + $response = $this->getService()->handle(1, [ + $variables[0]->env_variable => 'Test_SomeValue_0', + $variables[1]->env_variable => 'Test_SomeValue_1', + $variables[2]->env_variable => 'Test_SomeValue_2', + $variables[3]->env_variable => 'Test_SomeValue_3', + ]); + + $this->assertNotEmpty($response); + $this->assertInstanceOf(Collection::class, $response); + $this->assertEquals(1, $response->count(), 'Assert response has a single item in collection.'); + + $variable = $response->first(); + $this->assertObjectHasAttribute('id', $variable); + $this->assertObjectHasAttribute('key', $variable); + $this->assertObjectHasAttribute('value', $variable); + $this->assertSame($variables[0]->id, $variable->id); + $this->assertSame($variables[0]->env_variable, $variable->key); + $this->assertSame('Test_SomeValue_0', $variable->value); + } + + /** + * Test that all variables are processed correctly if admin flag is set. + */ + public function testValidatorShouldProcessAllVariablesWhenAdminFlagIsSet() + { + $variables = $this->getVariableCollection(); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables); + + $service = $this->getService(); + $service->setUserLevel(User::USER_LEVEL_ADMIN); + $response = $service->handle(1, [ + $variables[0]->env_variable => 'Test_SomeValue_0', + $variables[1]->env_variable => 'Test_SomeValue_1', + $variables[2]->env_variable => 'Test_SomeValue_2', + $variables[3]->env_variable => 'Test_SomeValue_3', + ]); + + $this->assertNotEmpty($response); + $this->assertInstanceOf(Collection::class, $response); + $this->assertEquals(4, $response->count(), 'Assert response has all four items in collection.'); + + $response->each(function ($variable, $key) use ($variables) { + $this->assertObjectHasAttribute('id', $variable); + $this->assertObjectHasAttribute('key', $variable); + $this->assertObjectHasAttribute('value', $variable); + $this->assertSame($variables[$key]->id, $variable->id); + $this->assertSame($variables[$key]->env_variable, $variable->key); + $this->assertSame('Test_SomeValue_' . $key, $variable->value); + }); + } + + /** + * Test that a DisplayValidationError is thrown when a variable is not validated. + */ + public function testValidatorShouldThrowExceptionWhenAValidationErrorIsEncountered() + { + $variables = $this->getVariableCollection(); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables); + + try { + $this->getService()->handle(1, [$variables[0]->env_variable => null]); + } catch (ValidationException $exception) { + $messages = $exception->validator->getMessageBag()->all(); + + $this->assertNotEmpty($messages); + $this->assertSame(2, count($messages)); + + // We only expect to get the first two variables form the getVariableCollection + // function here since those are the only two that are editable, and the others + // should be discarded and not validated. + for ($i = 0; $i < 2; $i++) { + $this->assertSame(trans('validation.required', [ + 'attribute' => trans('validation.internal.variable_value', ['env' => $variables[$i]->name]), + ]), $messages[$i]); + } + } + } + + /** + * Return a collection of fake variables to use for testing. + * + * @return \Illuminate\Support\Collection + */ + private function getVariableCollection(): Collection + { + return collect( + [ + factory(EggVariable::class)->states('editable', 'viewable')->make(), + factory(EggVariable::class)->states('editable')->make(), + factory(EggVariable::class)->states('viewable')->make(), + factory(EggVariable::class)->make(), + ] + ); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Servers\VariableValidatorService + */ + private function getService(): VariableValidatorService + { + return new VariableValidatorService( + $this->optionVariableRepository, + $this->serverRepository, + $this->serverVariableRepository, + $this->app->make(Factory::class) + ); + } +} diff --git a/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php b/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php new file mode 100644 index 000000000..50f1d5e7e --- /dev/null +++ b/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php @@ -0,0 +1,228 @@ +keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->subuserRepository = m::mock(SubuserRepositoryInterface::class); + $this->userRepository = m::mock(UserRepositoryInterface::class); + } + + /** + * Test that an account can be authenticated. + */ + public function testNonAdminAccountIsAuthenticated() + { + $user = factory(User::class)->make(['root_admin' => 0]); + $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('server_token'); + + $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + $this->assertNotEmpty($response); + $this->assertArrayHasKey('server', $response); + $this->assertArrayHasKey('token', $response); + $this->assertSame($server->uuid, $response['server']); + $this->assertSame('server_token', $response['token']); + } + + /** + * Test that an administrative user can access servers that they are not + * set as the owner of. + */ + public function testAdminAccountIsAuthenticated() + { + $user = factory(User::class)->make(['root_admin' => 1]); + $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('server_token'); + + $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + $this->assertNotEmpty($response); + $this->assertArrayHasKey('server', $response); + $this->assertArrayHasKey('token', $response); + $this->assertSame($server->uuid, $response['server']); + $this->assertSame('server_token', $response['token']); + } + + /** + * Test exception gets thrown if no server is passed into the function. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfNoServerIsProvided() + { + $this->getService()->handle('username', 'password', 1); + } + + /** + * Test that an exception is thrown if the user account exists but the wrong + * credentials are passed. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfUserDetailsAreIncorrect() + { + $user = factory(User::class)->make(); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->getService()->handle($user->username, 'wrongpassword', 1, '1234'); + } + + /** + * Test that an exception is thrown if no user account is found. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfNoUserAccountIsFound() + { + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', 'something']])->once()->andThrow(new RecordNotFoundException); + + $this->getService()->handle('something', 'password', 1, '1234'); + } + + /** + * Test that an exception is thrown if the user is not the owner of the server, + * is not a sub user and is not an administrator. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfUserDoesNotOwnServer() + { + $user = factory(User::class)->make(['root_admin' => 0]); + $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->subuserRepository->shouldReceive('getWithPermissionsUsingUserAndServer')->with($user->id, $server->id)->once()->andThrow(new RecordNotFoundException); + + $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + } + + /** + * Test that an exception is thrown if the requested server does not belong to + * the node that the request is made from. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfServerDoesNotExistOnCurrentNode() + { + $user = factory(User::class)->make(['root_admin' => 0]); + $server = factory(Server::class)->make(['node_id' => 2, 'owner_id' => $user->id]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + } + + /** + * Test that a suspended server throws an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testSuspendedServer() + { + $user = factory(User::class)->make(['root_admin' => 1]); + $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1, 'suspended' => 1]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + } + + /** + * Test that a server that is not yet installed throws an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testNotInstalledServer() + { + $user = factory(User::class)->make(['root_admin' => 1]); + $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1, 'installed' => 0]); + + $this->userRepository->shouldReceive('setColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user); + + $this->repository->shouldReceive('setColumns')->with(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->once()->andReturnSelf(); + $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); + + $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Sftp\AuthenticateUsingPasswordService + */ + private function getService(): AuthenticateUsingPasswordService + { + return new AuthenticateUsingPasswordService($this->keyProviderService, $this->repository, $this->subuserRepository, $this->userRepository); + } +} diff --git a/tests/Unit/Services/Subusers/PermissionCreationServiceTest.php b/tests/Unit/Services/Subusers/PermissionCreationServiceTest.php new file mode 100644 index 000000000..1581343ea --- /dev/null +++ b/tests/Unit/Services/Subusers/PermissionCreationServiceTest.php @@ -0,0 +1,55 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Subusers; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Services\Subusers\PermissionCreationService; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; + +class PermissionCreationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(PermissionRepositoryInterface::class); + $this->service = new PermissionCreationService($this->repository); + } + + /** + * Test that permissions can be assigned correctly. + */ + public function testPermissionsAreAssignedCorrectly() + { + $permissions = ['access-sftp']; + + $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('insert')->with([ + ['subuser_id' => 1, 'permission' => 'access-sftp'], + ])->once()->andReturn(true); + + $this->service->handle(1, $permissions); + $this->assertTrue(true); + } +} diff --git a/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php new file mode 100644 index 000000000..1784e2c57 --- /dev/null +++ b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php @@ -0,0 +1,207 @@ +connection = m::mock(ConnectionInterface::class); + $this->keyCreationService = m::mock(DaemonKeyCreationService::class); + $this->permissionService = m::mock(PermissionCreationService::class); + $this->subuserRepository = m::mock(SubuserRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + $this->userCreationService = m::mock(UserCreationService::class); + $this->userRepository = m::mock(UserRepositoryInterface::class); + } + + /** + * Test that a user without an existing account can be added as a subuser. + */ + public function testAccountIsCreatedForNewUser() + { + $permissions = ['test-1' => 'test:1', 'test-2' => null]; + $server = factory(Server::class)->make(); + $user = factory(User::class)->make([ + 'email' => 'known.1+test@example.com', + ]); + $subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andThrow(new RecordNotFoundException); + $this->userCreationService->shouldReceive('handle')->with(m::on(function ($data) use ($user) { + $subset = m::subset([ + 'email' => $user->email, + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ])->match($data); + + $username = substr(array_get($data, 'username', ''), 0, -3) === 'known.1test'; + + return $subset && $username; + }))->once()->andReturn($user); + + $this->subuserRepository->shouldReceive('create')->with(['user_id' => $user->id, 'server_id' => $server->id]) + ->once()->andReturn($subuser); + $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturnNull(); + $this->permissionService->shouldReceive('handle')->with($subuser->id, array_keys($permissions))->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($server, $user->email, array_keys($permissions)); + $this->assertInstanceOf(Subuser::class, $response); + $this->assertSame($subuser, $response); + } + + /** + * Test that an existing user can be added as a subuser. + */ + public function testExistingUserCanBeAddedAsASubuser() + { + $permissions = ['access-sftp']; + $server = factory(Server::class)->make(); + $user = factory(User::class)->make(); + $subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]); + + $this->serverRepository->shouldReceive('find')->with($server->id)->once()->andReturn($server); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->subuserRepository->shouldReceive('findCountWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andReturn(0); + + $this->subuserRepository->shouldReceive('create')->with(['user_id' => $user->id, 'server_id' => $server->id]) + ->once()->andReturn($subuser); + $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturnNull(); + $this->permissionService->shouldReceive('handle')->with($subuser->id, $permissions)->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($server->id, $user->email, $permissions); + $this->assertInstanceOf(Subuser::class, $response); + $this->assertSame($subuser, $response); + } + + /** + * Test that an exception gets thrown if the subuser is actually the server owner. + */ + public function testExceptionIsThrownIfUserIsServerOwner() + { + $user = factory(User::class)->make(); + $server = factory(Server::class)->make(['owner_id' => $user->id]); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + + try { + $this->getService()->handle($server, $user->email, []); + } catch (DisplayException $exception) { + $this->assertInstanceOf(UserIsServerOwnerException::class, $exception); + $this->assertEquals(trans('exceptions.subusers.user_is_owner'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the user is already added as a subuser. + */ + public function testExceptionIsThrownIfUserIsAlreadyASubuser() + { + $user = factory(User::class)->make(); + $server = factory(Server::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->userRepository->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->subuserRepository->shouldReceive('findCountWhere')->with([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ])->once()->andReturn(1); + + try { + $this->getService()->handle($server, $user->email, []); + } catch (DisplayException $exception) { + $this->assertInstanceOf(ServerSubuserExistsException::class, $exception); + $this->assertEquals(trans('exceptions.subusers.subuser_exists'), $exception->getMessage()); + } + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Subusers\SubuserCreationService + */ + private function getService(): SubuserCreationService + { + return new SubuserCreationService( + $this->connection, + $this->keyCreationService, + $this->permissionService, + $this->serverRepository, + $this->subuserRepository, + $this->userCreationService, + $this->userRepository + ); + } +} diff --git a/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php new file mode 100644 index 000000000..b32b272ed --- /dev/null +++ b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php @@ -0,0 +1,74 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Subusers; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Subuser; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Services\Subusers\SubuserDeletionService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; + +class SubuserDeletionServiceTest extends TestCase +{ + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + private $connection; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService|\Mockery\Mock + */ + private $keyDeletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->keyDeletionService = m::mock(DaemonKeyDeletionService::class); + $this->repository = m::mock(SubuserRepositoryInterface::class); + } + + /** + * Test that a subuser is deleted correctly. + */ + public function testSubuserIsDeleted() + { + $subuser = factory(Subuser::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->keyDeletionService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id)->once()->andReturnNull(); + $this->repository->shouldReceive('delete')->with($subuser->id)->once()->andReturn(1); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->getService()->handle($subuser); + $this->assertTrue(true); + } + + /** + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Subusers\SubuserDeletionService + */ + private function getService(): SubuserDeletionService + { + return new SubuserDeletionService($this->connection, $this->keyDeletionService, $this->repository); + } +} diff --git a/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php new file mode 100644 index 000000000..6ca75c0fe --- /dev/null +++ b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php @@ -0,0 +1,147 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Subusers; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\User; +use GuzzleHttp\Psr7\Response; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Tests\Traits\MocksRequestException; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Subusers\SubuserUpdateService; +use Pterodactyl\Services\Subusers\PermissionCreationService; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SubuserUpdateServiceTest extends TestCase +{ + use MocksRequestException; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + private $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock + */ + private $daemonRepository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock + */ + private $keyProviderService; + + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface|\Mockery\Mock + */ + private $permissionRepository; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService|\Mockery\Mock + */ + private $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock + */ + private $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class); + $this->keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->permissionRepository = m::mock(PermissionRepositoryInterface::class); + $this->permissionService = m::mock(PermissionCreationService::class); + $this->repository = m::mock(SubuserRepositoryInterface::class); + } + + /** + * Test that permissions are updated in the database. + */ + public function testPermissionsAreUpdated() + { + $subuser = factory(Subuser::class)->make(); + $subuser->setRelation('server', factory(Server::class)->make()); + $subuser->setRelation('user', factory(User::class)->make()); + + $this->repository->shouldReceive('loadServerAndUserRelations')->with($subuser)->once()->andReturn($subuser); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturn(1); + $this->permissionService->shouldReceive('handle')->with($subuser->id, ['some-permission'])->once()->andReturnNull(); + + $this->keyProviderService->shouldReceive('handle')->with($subuser->server, $subuser->user, false)->once()->andReturn('test123'); + $this->daemonRepository->shouldReceive('setServer')->with($subuser->server)->once()->andReturnSelf(); + $this->daemonRepository->shouldReceive('revokeAccessKey')->with('test123')->once()->andReturn(new Response); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->getService()->handle($subuser, ['some-permission']); + $this->assertTrue(true); + } + + /** + * Test that an exception is thrown if the daemon connection fails. + */ + public function testExceptionIsThrownIfDaemonConnectionFails() + { + $this->configureExceptionMock(); + + $subuser = factory(Subuser::class)->make(); + $subuser->setRelation('server', factory(Server::class)->make()); + $subuser->setRelation('user', factory(User::class)->make()); + + $this->repository->shouldReceive('loadServerAndUserRelations')->with($subuser)->once()->andReturn($subuser); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturn(1); + $this->permissionService->shouldReceive('handle')->with($subuser->id, [])->once()->andReturnNull(); + + $this->keyProviderService->shouldReceive('handle')->with($subuser->server, $subuser->user, false)->once()->andReturn('test123'); + $this->daemonRepository->shouldReceive('setServer')->with($subuser->server)->once()->andThrow($this->getExceptionMock()); + $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getService()->handle($subuser, []); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } + + /** + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Subusers\SubuserUpdateService + */ + private function getService(): SubuserUpdateService + { + return new SubuserUpdateService( + $this->connection, + $this->keyProviderService, + $this->daemonRepository, + $this->permissionService, + $this->permissionRepository, + $this->repository + ); + } +} diff --git a/tests/Unit/Services/Users/ToggleTwoFactorServiceTest.php b/tests/Unit/Services/Users/ToggleTwoFactorServiceTest.php new file mode 100644 index 000000000..0b8f453cc --- /dev/null +++ b/tests/Unit/Services/Users/ToggleTwoFactorServiceTest.php @@ -0,0 +1,128 @@ +config = m::mock(Repository::class); + $this->encrypter = m::mock(Encrypter::class); + $this->google2FA = m::mock(Google2FA::class); + $this->repository = m::mock(UserRepositoryInterface::class); + + $this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.window')->once()->andReturn(self::TEST_WINDOW_INT); + $this->encrypter->shouldReceive('decrypt')->with(self::USER_TOTP_SECRET)->once()->andReturn(self::DECRYPTED_USER_SECRET); + } + + /** + * Test that 2FA can be enabled for a user. + */ + public function testTwoFactorIsEnabledForUser() + { + $model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]); + + $this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => true, + ])->once()->andReturnNull(); + + $this->assertTrue($this->getService()->handle($model, 'test-token')); + } + + /** + * Test that 2FA can be disabled for a user. + */ + public function testTwoFactorIsDisabled() + { + $model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => true]); + + $this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => false, + ])->once()->andReturnNull(); + + $this->assertTrue($this->getService()->handle($model, 'test-token')); + } + + /** + * Test that 2FA will remain disabled for a user. + */ + public function testTwoFactorRemainsDisabledForUser() + { + $model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]); + + $this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => false, + ])->once()->andReturnNull(); + + $this->assertTrue($this->getService()->handle($model, 'test-token', false)); + } + + /** + * Test that an exception is thrown if the token provided is invalid. + * + * @expectedException \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + */ + public function testExceptionIsThrownIfTokenIsInvalid() + { + $model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET]); + $this->google2FA->shouldReceive('verifyKey')->once()->andReturn(false); + + $this->getService()->handle($model, 'test-token'); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Users\ToggleTwoFactorService + */ + private function getService(): ToggleTwoFactorService + { + return new ToggleTwoFactorService($this->encrypter, $this->google2FA, $this->config, $this->repository); + } +} diff --git a/tests/Unit/Services/Users/TwoFactorSetupServiceTest.php b/tests/Unit/Services/Users/TwoFactorSetupServiceTest.php new file mode 100644 index 000000000..8cb097537 --- /dev/null +++ b/tests/Unit/Services/Users/TwoFactorSetupServiceTest.php @@ -0,0 +1,77 @@ +config = m::mock(Repository::class); + $this->encrypter = m::mock(Encrypter::class); + $this->google2FA = m::mock(Google2FA::class); + $this->repository = m::mock(UserRepositoryInterface::class); + } + + /** + * Test that the correct data is returned. + */ + public function testSecretAndImageAreReturned() + { + $model = factory(User::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.bytes')->once()->andReturn(32); + $this->google2FA->shouldReceive('generateSecretKey')->with(32)->once()->andReturn('secretKey'); + $this->config->shouldReceive('get')->with('app.name')->once()->andReturn('CompanyName'); + $this->google2FA->shouldReceive('getQRCodeGoogleUrl')->with('CompanyName', $model->email, 'secretKey')->once()->andReturn('http://url.com'); + $this->encrypter->shouldReceive('encrypt')->with('secretKey')->once()->andReturn('encryptedSecret'); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, ['totp_secret' => 'encryptedSecret'])->once()->andReturnNull(); + + $response = $this->getService()->handle($model); + $this->assertNotEmpty($response); + $this->assertSame('http://url.com', $response); + } + + /** + * Return an instance of the service to test with mocked dependencies. + * + * @return \Pterodactyl\Services\Users\TwoFactorSetupService + */ + private function getService(): TwoFactorSetupService + { + return new TwoFactorSetupService($this->config, $this->encrypter, $this->google2FA, $this->repository); + } +} diff --git a/tests/Unit/Services/Users/UserCreationServiceTest.php b/tests/Unit/Services/Users/UserCreationServiceTest.php new file mode 100644 index 000000000..4012dc655 --- /dev/null +++ b/tests/Unit/Services/Users/UserCreationServiceTest.php @@ -0,0 +1,157 @@ +connection = m::mock(ConnectionInterface::class); + $this->hasher = m::mock(Hasher::class); + $this->passwordService = m::mock(TemporaryPasswordService::class); + $this->repository = m::mock(UserRepositoryInterface::class); + } + + /** + * Test that a user is created when a password is passed. + */ + public function testUserIsCreatedWhenPasswordIsProvided() + { + $user = factory(User::class)->make(); + + $this->hasher->shouldReceive('make')->with('raw-password')->once()->andReturn('enc-password'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'password' => 'enc-password', + 'uuid' => $this->getKnownUuid(), + ], true, true)->once()->andReturn($user); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle([ + 'password' => 'raw-password', + ]); + + $this->assertNotNull($response); + Notification::assertSentTo($user, AccountCreated::class, function ($notification) use ($user) { + $this->assertSame($user, $notification->user); + $this->assertNull($notification->token); + + return true; + }); + } + + /** + * Test that a UUID passed in the submission data is not used when + * creating the user. + */ + public function testUuidPassedInDataIsIgnored() + { + $user = factory(User::class)->make(); + + $this->hasher->shouldReceive('make')->andReturn('enc-password'); + $this->connection->shouldReceive('beginTransaction')->andReturnNull(); + $this->repository->shouldReceive('create')->with([ + 'password' => 'enc-password', + 'uuid' => $this->getKnownUuid(), + ], true, true)->once()->andReturn($user); + $this->connection->shouldReceive('commit')->andReturnNull(); + + $response = $this->getService()->handle([ + 'password' => 'raw-password', + 'uuid' => 'test-uuid', + ]); + + $this->assertNotNull($response); + $this->assertInstanceOf(User::class, $response); + Notification::assertSentTo($user, AccountCreated::class, function ($notification) use ($user) { + $this->assertSame($user, $notification->user); + $this->assertNull($notification->token); + + return true; + }); + } + + /** + * Test that a user is created with a random password when no password is provided. + */ + public function testUserIsCreatedWhenNoPasswordIsProvided() + { + $user = factory(User::class)->make(); + + $this->hasher->shouldNotReceive('make'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->hasher->shouldReceive('make')->once()->andReturn('created-enc-password'); + $this->passwordService->shouldReceive('handle')->with($user->email)->once()->andReturn('random-token'); + + $this->repository->shouldReceive('create')->with([ + 'password' => 'created-enc-password', + 'email' => $user->email, + 'uuid' => $this->getKnownUuid(), + ], true, true)->once()->andReturn($user); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle([ + 'email' => $user->email, + ]); + + $this->assertNotNull($response); + $this->assertInstanceOf(User::class, $response); + Notification::assertSentTo($user, AccountCreated::class, function ($notification) use ($user) { + $this->assertSame($user, $notification->user); + $this->assertSame('random-token', $notification->token); + + return true; + }); + } + + /** + * Return a new instance of the service using mocked dependencies. + * + * @return \Pterodactyl\Services\Users\UserCreationService + */ + private function getService(): UserCreationService + { + return new UserCreationService($this->connection, $this->hasher, $this->passwordService, $this->repository); + } +} diff --git a/tests/Unit/Services/Users/UserDeletionServiceTest.php b/tests/Unit/Services/Users/UserDeletionServiceTest.php new file mode 100644 index 000000000..2876ce147 --- /dev/null +++ b/tests/Unit/Services/Users/UserDeletionServiceTest.php @@ -0,0 +1,103 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Users; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\User; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Services\Users\UserDeletionService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class UserDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Translation\Translator + */ + protected $translator; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Users\UserDeletionService + */ + protected $service; + + /** + * @var User + */ + protected $user; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->user = factory(User::class)->make(); + $this->repository = m::mock(UserRepositoryInterface::class); + $this->translator = m::mock(Translator::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + + $this->service = new UserDeletionService( + $this->serverRepository, + $this->translator, + $this->repository + ); + } + + /** + * Test that a user is deleted if they have no servers. + */ + public function testUserIsDeletedIfNoServersAreAttachedToAccount() + { + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['owner_id', '=', $this->user->id]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with($this->user->id)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle($this->user->id)); + } + + /** + * Test that an exception is thrown if trying to delete a user with servers. + * + * @expectedException \Pterodactyl\Exceptions\DisplayException + */ + public function testExceptionIsThrownIfServersAreAttachedToAccount() + { + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['owner_id', '=', $this->user->id]])->once()->andReturn(1); + $this->translator->shouldReceive('trans')->with('admin/user.exceptions.user_has_servers')->once()->andReturnNull(); + + $this->service->handle($this->user->id); + } + + /** + * Test that the function supports passing in a model or an ID. + */ + public function testModelCanBePassedInPlaceOfUserId() + { + $this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([['owner_id', '=', $this->user->id]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with($this->user->id)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle($this->user)); + } +} diff --git a/tests/Unit/Services/Users/UserUpdateServiceTest.php b/tests/Unit/Services/Users/UserUpdateServiceTest.php new file mode 100644 index 000000000..a686abefa --- /dev/null +++ b/tests/Unit/Services/Users/UserUpdateServiceTest.php @@ -0,0 +1,138 @@ +hasher = m::mock(Hasher::class); + $this->repository = m::mock(UserRepositoryInterface::class); + $this->revocationService = m::mock(RevokeMultipleDaemonKeysService::class); + } + + /** + * Test that the handle function does not attempt to hash a password if no + * password is provided or the password is null. + * + * @dataProvider badPasswordDataProvider + */ + public function testUpdateUserWithoutTouchingHasherIfNoPasswordPassed(array $data) + { + $user = factory(User::class)->make(); + $this->revocationService->shouldReceive('getExceptions')->withNoArgs()->once()->andReturn([]); + $this->repository->shouldReceive('update')->with($user->id, ['test-data' => 'value'])->once()->andReturnNull(); + + $response = $this->getService()->handle($user, $data); + $this->assertInstanceOf(Collection::class, $response); + $this->assertTrue($response->has('model')); + $this->assertTrue($response->has('exceptions')); + } + + /** + * Provide a test data set with passwords that should not be hashed. + * + * @return array + */ + public function badPasswordDataProvider(): array + { + return [ + [['test-data' => 'value']], + [['test-data' => 'value', 'password' => null]], + [['test-data' => 'value', 'password' => '']], + [['test-data' => 'value', 'password' => 0]], + ]; + } + + /** + * Test that the handle function hashes a password if passed in the data array. + */ + public function testUpdateUserAndHashPasswordIfProvided() + { + $user = factory(User::class)->make(); + $this->hasher->shouldReceive('make')->with('raw_pass')->once()->andReturn('enc_pass'); + $this->revocationService->shouldReceive('getExceptions')->withNoArgs()->once()->andReturn([]); + $this->repository->shouldReceive('update')->with($user->id, ['password' => 'enc_pass'])->once()->andReturnNull(); + + $response = $this->getService()->handle($user, ['password' => 'raw_pass']); + $this->assertInstanceOf(Collection::class, $response); + $this->assertTrue($response->has('model')); + $this->assertTrue($response->has('exceptions')); + } + + /** + * Test that an admin can revoke a user's administrative status. + */ + public function testAdministrativeUserRevokingAdminStatus() + { + $user = factory(User::class)->make(['root_admin' => true]); + $service = $this->getService(); + $service->setUserLevel(User::USER_LEVEL_ADMIN); + + $this->revocationService->shouldReceive('handle')->with($user, false)->once()->andReturnNull(); + $this->revocationService->shouldReceive('getExceptions')->withNoArgs()->once()->andReturn([]); + $this->repository->shouldReceive('update')->with($user->id, ['root_admin' => false])->once()->andReturnNull(); + + $response = $service->handle($user, ['root_admin' => false]); + $this->assertInstanceOf(Collection::class, $response); + $this->assertTrue($response->has('model')); + $this->assertTrue($response->has('exceptions')); + } + + /** + * Test that a normal user is unable to set an administrative status for themselves. + */ + public function testNormalUserShouldNotRevokeAdminStatus() + { + $user = factory(User::class)->make(['root_admin' => false]); + $service = $this->getService(); + $service->setUserLevel(User::USER_LEVEL_USER); + + $this->revocationService->shouldReceive('getExceptions')->withNoArgs()->once()->andReturn([]); + $this->repository->shouldReceive('update')->with($user->id, [])->once()->andReturnNull(); + + $response = $service->handle($user, ['root_admin' => true]); + $this->assertInstanceOf(Collection::class, $response); + $this->assertTrue($response->has('model')); + $this->assertTrue($response->has('exceptions')); + } + + /** + * Return an instance of the service for testing. + * + * @return \Pterodactyl\Services\Users\UserUpdateService + */ + private function getService(): UserUpdateService + { + return new UserUpdateService($this->hasher, $this->revocationService, $this->repository); + } +}