diff --git a/.env.example b/.env.example index fa7e20965..ed3a56b68 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,7 @@ DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret -CACHE_DRIVER=file +CACHE_DRIVER=memcached SESSION_DRIVER=database MAIL_DRIVER=smtp diff --git a/.gitignore b/.gitignore index ad901f653..a30c114f5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ Vagrantfile node_modules yarn.lock +node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ee47a5c..8b856fd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,23 +3,66 @@ 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.6.0-pre.1 +## v0.6.0-pre.4 (Courageous Carniadactylus) +### Fixed +* `[pre.3]` — Fixes bug in cache handler that doesn't cache against the user making the request. Would have allowed for users to access servers not belonging to themselves in production. +* `[pre.3]` — Fixes misnamed MySQL column that was causing the inability to delete certain port ranges from the database. +* `[pre.3]` — Fixes bug preventing rebuilding server containers through the Admin CP. + +### Added +* New cache policy for ServerPolicy to avoid making 15+ queries per page load when confirming if a user has permission to perform an action. + +## v0.6.0-pre.3 (Courageous Carniadactylus) +### Fixed +* `[pre.2]` — Fixes bug where servers could not be manually deployed to nodes due to a broken SQL call. +* `[pre.2]` — Fixes inability to edit a server due to owner_id issues. +* `[pre.2]` — Fixes bug when trying to add new subusers. +* Emails sending with 'Pterodactyl Panel' as the from name. Now configurable by using `php artisan pterodactyl:mail` to update. +* `[pre.2]` — Fixes inability to delete accounts due to SQL changes. +* `[pre.2]` — Fixes bug with checking power-permissions that showed the wrong buttons. Also adds check back to sidebar to only show options a user can use. +* `[pre.2]` — Fixes allocation listing on node allocations tab as well as bug preventing deletion of port. +* `[pre.2]` — Fixes bug in services that prevented saving updated settings or creating new services. + +### Changed +* `[pre.2]` — File Manager now displays relevant information on all screen sizes, and includes better button clicking mechanics for dropdown menu. +* Reduced the number of database queries being executed when viewing a specific server. This is done by caching the query for up to 60 minutes in memcached. +* User creation emails include more information and are sent by the event listener rather than the repository. +* Account password reset emails now auto-fill the email when clicking the link. + +### Added +* Notifications when a user is added or removed as a subuser for a server. + +## v0.6.0-pre.2 (Courageous Carniadactylus) +### Fixed +* `[pre.1]` — Fixes bug with database seeders that prevented correctly installing the panel. + +### Changed +* `[pre.1]` — Moved around navigation bar on fronted to make it more obvious where logout and admin buttons were, as well as use the right icon for server listing. + +## v0.6.0-pre.1 (Courageous Carniadactylus) ### Added * Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. * Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. * Users can now have a username as well as client name assigned to their account. * Ability to create a node through the CLI using `pterodactyl:node` as well as locations via `pterodactyl:location`. * New theme (AdminLTE) for front-end with tweaks to backend files to work properly with it. +* Add support for PhraseApp's in-context editor ### Fixed * Bug causing error logs to be spammed if someone timed out on an ajax based page. * Fixes edge case where specific server names could cause daemon errors due to an invalid SFTP username being created by the panel. +* Fixes sessions being removed on browser close, and set sessions to idle for up to 3 hours before being marked as expired. ### Changed * Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. * User model now defines mass assignment fields using `$fillable` rather than `$guarded`. +* 2FA checkpoint on login is now its own page, and not an AJAX based call. Improves security on that front. +* Updated Server model code to be more efficient, as well as make life easier for backend changes and work. ### Deprecated +* `Server::getUserDaemonSecret(Server $server)` was removed and replaced with `User::daemonSecret(Server $server)` in order to clean up models. +* `Server::getByUUID()` was replaced with `Server::byUuid()` as well as various other functions through-out the Server model. +* `Server::getHeaders()` was removed and replaced with `Server::getClient()` which returns a Guzzle Client with the correct headers already assigned. ## v0.5.6 (Bodacious Boreopterus) ### Added diff --git a/README.md b/README.md index 30f0062b4..c47e6f101 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ +[![Logo Image](https://cdn.pterodactyl.io/logos/Banner%20Logo%20Black@2x.png)](https://pterodactyl.io) + ## Pterodactyl Panel -Pterodactyl is the free game server management panel designed by users, for users. Featuring support for Vanilla Minecraft, Spigot, Source Dedicated Servers, BungeeCord, and many more. Pterodactyl is built on the `Laravel PHP Framework (v5.3)`. +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. ## Support & Documentation -Support for using Pterodactyl can be found on our [wiki](https://github.com/Pterodactyl/Panel/wiki) or on our [Discord chat](https://discord.gg/0gYt8oU8QOkDhKLS). +Support for using Pterodactyl can be found on our [Documentation Website](https://docs.pterodactyl.io), our [Discord Chat](https://discord.gg/QRDZvVm), or via our [Forums](https://forums.pterodactyl.io). ## License ``` -Copyright (c) 2015 - 2017 Dane Everitt +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 @@ -39,8 +41,6 @@ Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICEN Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) -BinaryJS - [license](https://github.com/binaryjs/binaryjs/blob/master/LICENSE) - [homepage](http://binaryjs.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) @@ -51,12 +51,12 @@ FontAwesome - [license](http://fontawesome.io/license/) - [homepage](http://font FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation) -FuelUX - [license](https://github.com/ExactTarget/fuelux/blob/master/LICENSE) - [homepage](http://getfuelux.com) - jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com) jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl) +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/) Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io) @@ -74,6 +74,6 @@ Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apac Some images used within Pterodactyl are Copyright (c) their respective owners. -`/public/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/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/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/) +`/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/UpdateEmailSettings.php b/app/Console/Commands/UpdateEmailSettings.php index 620982b98..fabfa469d 100644 --- a/app/Console/Commands/UpdateEmailSettings.php +++ b/app/Console/Commands/UpdateEmailSettings.php @@ -36,6 +36,7 @@ class UpdateEmailSettings extends Command protected $signature = 'pterodactyl:mail {--driver=} {--email=} + {--from-name=} {--host=} {--port=} {--username=} @@ -137,6 +138,7 @@ class UpdateEmailSettings extends Command } $variables['MAIL_FROM'] = is_null($this->option('email')) ? $this->ask('Email address emails should originate from') : $this->option('email'); + $variables['MAIL_FROM_NAME'] = is_null($this->option('from-name')) ? $this->ask('Name emails should appear to be from') : $this->option('from-name'); $variables['MAIL_ENCRYPTION'] = 'tls'; $bar = $this->output->createProgressBar(count($variables)); @@ -155,6 +157,9 @@ class UpdateEmailSettings extends Command 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 index 085b081df..aa3dcfe7f 100644 --- a/app/Console/Commands/UpdateEnvironment.php +++ b/app/Console/Commands/UpdateEnvironment.php @@ -134,6 +134,10 @@ class UpdateEnvironment extends Command $variables['APP_TIMEZONE'] = $this->option('timezone'); } + $variables['APP_THEME'] = 'pterodactyl'; + $variables['CACHE_DRIVER'] = 'memcached'; + $variables['SESSION_DRIVER'] = 'database'; + $bar = $this->output->createProgressBar(count($variables)); $this->line('Writing new environment configuration to file.'); @@ -150,6 +154,9 @@ class UpdateEnvironment extends Command file_put_contents($file, $envContents); $bar->finish(); + + $this->line('Updating evironment configuration cache file.'); + $this->call('config:cache'); echo "\n"; } } diff --git a/app/Models/ServiceOptions.php b/app/Events/Server/Created.php similarity index 60% rename from app/Models/ServiceOptions.php rename to app/Events/Server/Created.php index fe7e23f10..d5a308876 100644 --- a/app/Models/ServiceOptions.php +++ b/app/Events/Server/Created.php @@ -22,42 +22,24 @@ * SOFTWARE. */ -namespace Pterodactyl\Models; +namespace Pterodactyl\Events\Server; -use Illuminate\Database\Eloquent\Model; +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; -class ServiceOptions extends Model +class Created { - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'service_options'; + use SerializesModels; + + public $server; /** - * Fields that are not mass assignable. + * Create a new event instance. * - * @var array + * @return void */ - protected $guarded = ['id', 'created_at', 'updated_at']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'parent_service' => 'integer', - ]; - - /** - * Gets all variables associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function variables() - { - return $this->hasMany(ServiceVariables::class, 'option_id'); - } + public function __construct(Server $server) + { + $this->server = $server; + } } diff --git a/app/Events/Server/Creating.php b/app/Events/Server/Creating.php new file mode 100644 index 000000000..cbba63eb1 --- /dev/null +++ b/app/Events/Server/Creating.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Deleted.php b/app/Events/Server/Deleted.php new file mode 100644 index 000000000..b36dca12d --- /dev/null +++ b/app/Events/Server/Deleted.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Deleted +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Deleting.php b/app/Events/Server/Deleting.php new file mode 100644 index 000000000..bb18e1ee9 --- /dev/null +++ b/app/Events/Server/Deleting.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/ServerDeleted.php b/app/Events/Server/Saved.php similarity index 89% rename from app/Events/ServerDeleted.php rename to app/Events/Server/Saved.php index 95f8e33e1..08700e3cb 100644 --- a/app/Events/ServerDeleted.php +++ b/app/Events/Server/Saved.php @@ -22,11 +22,12 @@ * SOFTWARE. */ -namespace Pterodactyl\Events; +namespace Pterodactyl\Events\Server; +use Pterodactyl\Models\Server; use Illuminate\Queue\SerializesModels; -class ServerDeleted +class Saved { use SerializesModels; @@ -37,8 +38,8 @@ class ServerDeleted * * @return void */ - public function __construct($id) + public function __construct(Server $server) { - $this->server = $id; + $this->server = $server; } } diff --git a/app/Events/Server/Saving.php b/app/Events/Server/Saving.php new file mode 100644 index 000000000..2c6d3cb4e --- /dev/null +++ b/app/Events/Server/Saving.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Saving +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Updated.php b/app/Events/Server/Updated.php new file mode 100644 index 000000000..0310e20c6 --- /dev/null +++ b/app/Events/Server/Updated.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Updated +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Server/Updating.php b/app/Events/Server/Updating.php new file mode 100644 index 000000000..f333ede6b --- /dev/null +++ b/app/Events/Server/Updating.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Server; + +use Pterodactyl\Models\Server; +use Illuminate\Queue\SerializesModels; + +class Updating +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Server $server) + { + $this->server = $server; + } +} diff --git a/app/Events/Subuser/Created.php b/app/Events/Subuser/Created.php new file mode 100644 index 000000000..7b3be9597 --- /dev/null +++ b/app/Events/Subuser/Created.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Created +{ + use SerializesModels; + + public $subuser; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Creating.php b/app/Events/Subuser/Creating.php new file mode 100644 index 000000000..353497e24 --- /dev/null +++ b/app/Events/Subuser/Creating.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + public $subuser; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Deleted.php b/app/Events/Subuser/Deleted.php new file mode 100644 index 000000000..3ffe3afe8 --- /dev/null +++ b/app/Events/Subuser/Deleted.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Deleted +{ + use SerializesModels; + + public $subuser; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/Subuser/Deleting.php b/app/Events/Subuser/Deleting.php new file mode 100644 index 000000000..da812019b --- /dev/null +++ b/app/Events/Subuser/Deleting.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\Subuser; + +use Pterodactyl\Models\Subuser; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + public $subuser; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Subuser $subuser) + { + $this->subuser = $subuser; + } +} diff --git a/app/Events/User/Created.php b/app/Events/User/Created.php new file mode 100644 index 000000000..a00bdd450 --- /dev/null +++ b/app/Events/User/Created.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Created +{ + use SerializesModels; + + public $user; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Events/User/Creating.php b/app/Events/User/Creating.php new file mode 100644 index 000000000..39a50f0c8 --- /dev/null +++ b/app/Events/User/Creating.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Creating +{ + use SerializesModels; + + public $user; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Events/User/Deleted.php b/app/Events/User/Deleted.php new file mode 100644 index 000000000..348c859c9 --- /dev/null +++ b/app/Events/User/Deleted.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Deleted +{ + use SerializesModels; + + public $user; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Events/User/Deleting.php b/app/Events/User/Deleting.php new file mode 100644 index 000000000..5bbd91366 --- /dev/null +++ b/app/Events/User/Deleting.php @@ -0,0 +1,45 @@ +. + * + * 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\Events\User; + +use Pterodactyl\Models\User; +use Illuminate\Queue\SerializesModels; + +class Deleting +{ + use SerializesModels; + + public $user; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(User $user) + { + $this->user = $user; + } +} diff --git a/app/Extensions/PhraseAppTranslator.php b/app/Extensions/PhraseAppTranslator.php new file mode 100644 index 000000000..5a8d4a84e --- /dev/null +++ b/app/Extensions/PhraseAppTranslator.php @@ -0,0 +1,46 @@ +. + * + * 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\Extensions; + +use Illuminate\Translation\Translator as LaravelTranslator; + +class PhraseAppTranslator extends LaravelTranslator +{ + /** + * Get the translation for the given key. + * + * @param string $key + * @param array $replace + * @param string|null $locale + * @param bool $fallback + * @return string|array|null + */ + public function get($key, array $replace = [], $locale = null, $fallback = true) + { + $key = substr($key, strpos($key, '.') + 1); + + return "{{__phrase_${key}__}}"; + } +} diff --git a/app/Http/Controllers/API/LocationController.php b/app/Http/Controllers/API/LocationController.php index 3bae975d8..fcaf5a71c 100755 --- a/app/Http/Controllers/API/LocationController.php +++ b/app/Http/Controllers/API/LocationController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\API; -use DB; use Illuminate\Http\Request; use Pterodactyl\Models\Location; @@ -49,11 +48,12 @@ class LocationController extends BaseController */ public function lists(Request $request) { - return Location::select('locations.*', DB::raw('GROUP_CONCAT(nodes.id) as nodes')) - ->join('nodes', 'locations.id', '=', 'nodes.location') - ->groupBy('locations.id') - ->get()->each(function ($location) { - $location->nodes = explode(',', $location->nodes); - })->all(); + return Location::with('nodes')->get()->map(function ($item) { + $item->nodes->transform(function ($item) { + return collect($item)->only(['id', 'name', 'fqdn', 'scheme', 'daemonListen', 'daemonSFTP']); + }); + + return $item; + })->toArray(); } } diff --git a/app/Http/Controllers/API/NodeController.php b/app/Http/Controllers/API/NodeController.php index 0eda949f4..b0cfb452a 100755 --- a/app/Http/Controllers/API/NodeController.php +++ b/app/Http/Controllers/API/NodeController.php @@ -24,6 +24,7 @@ namespace Pterodactyl\Http\Controllers\API; +use Log; use Pterodactyl\Models; use Illuminate\Http\Request; use Dingo\Api\Exception\ResourceException; @@ -96,15 +97,21 @@ class NodeController extends BaseController public function create(Request $request) { try { - $node = new NodeRepository; - $new = $node->create($request->all()); + $repo = new NodeRepository; + $node = $repo->create($request->only([ + 'name', 'location_id', 'public', 'fqdn', + 'scheme', 'memory', 'memory_overallocate', + 'disk', 'disk_overallocate', 'daemonBase', + 'daemonSFTP', 'daemonListen', + ])); - return ['id' => $new]; + return ['id' => $repo->id]; } catch (DisplayValidationException $ex) { throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); } catch (DisplayException $ex) { throw new ResourceException($ex->getMessage()); - } catch (\Exception $e) { + } catch (\Exception $ex) { + Log::error($ex); throw new BadRequestHttpException('There was an error while attempting to add this node to the system.'); } } @@ -124,88 +131,35 @@ class NodeController extends BaseController */ public function view(Request $request, $id, $fields = null) { - $node = Models\Node::where('id', $id); + $node = Models\Node::with('allocations')->where('id', $id)->first(); + if (! $node) { + throw new NotFoundHttpException('No node by that ID was found.'); + } + + $node->allocations->transform(function ($item) { + return collect($item)->only([ + 'id', 'ip', 'ip_alias', 'port', 'server_id', + ]); + }); if (! is_null($request->input('fields'))) { - foreach (explode(',', $request->input('fields')) as $field) { - if (! empty($field)) { - $node->addSelect($field); - } + $fields = explode(',', $request->input('fields')); + if (! empty($fields) && is_array($fields)) { + return collect($node)->only($fields); } } - try { - if (! $node->first()) { - throw new NotFoundHttpException('No node by that ID was found.'); - } - - return [ - 'node' => $node->first(), - 'allocations' => [ - 'assigned' => Models\Allocation::where('node', $id)->whereNotNull('assigned_to')->get(), - 'unassigned' => Models\Allocation::where('node', $id)->whereNull('assigned_to')->get(), - ], - ]; - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } + return $node; } public function config(Request $request, $id) { - if (! $request->secure()) { - throw new BadRequestHttpException('This API route can only be accessed using a secure connection.'); - } - $node = Models\Node::where('id', $id)->first(); if (! $node) { throw new NotFoundHttpException('No node by that ID was found.'); } - return [ - 'web' => [ - 'listen' => $node->daemonListen, - 'host' => '0.0.0.0', - 'ssl' => [ - 'enabled' => ($node->scheme === 'https'), - 'certificate' => '/etc/certs/' . $node->fqdn . '/fullchain.pem', - 'key' => '/etc/certs/' . $node->fqdn . '/privkey.pem', - ], - ], - 'docker' => [ - 'socket' => '/var/run/docker.sock', - 'autoupdate_images' => true, - ], - 'sftp' => [ - 'path' => $node->daemonBase, - 'port' => (int) $node->daemonSFTP, - 'container' => 'ptdl-sftp', - ], - 'query' => [ - 'kill_on_fail' => true, - 'fail_limit' => 5, - ], - 'logger' => [ - 'path' => 'logs/', - 'src' => false, - 'level' => 'info', - 'period' => '1d', - 'count' => 3, - ], - 'remote' => [ - 'base' => config('app.url'), - 'download' => route('remote.download'), - 'installed' => route('remote.install'), - ], - 'uploads' => [ - 'size_limit' => $node->upload_size, - ], - 'keys' => [ - $node->daemonSecret, - ], - ]; + return $node->getConfigurationAsJson(); } /** @@ -219,12 +173,7 @@ class NodeController extends BaseController */ public function allocations(Request $request) { - $allocations = Models\Allocation::all(); - if ($allocations->count() < 1) { - throw new NotFoundHttpException('No allocations have been created.'); - } - - return $allocations; + return Models\Allocation::all()->toArray(); } /** @@ -238,18 +187,7 @@ class NodeController extends BaseController */ public function allocationsView(Request $request, $id) { - $query = Models\Allocation::where('assigned_to', $id)->get(); - try { - if (empty($query)) { - throw new NotFoundHttpException('No allocations for that server were found.'); - } - - return $query; - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } + return Models\Allocation::where('server_id', $id)->get()->toArray(); } /** diff --git a/app/Http/Controllers/API/ServerController.php b/app/Http/Controllers/API/ServerController.php index b24cdac62..e6b2add2c 100755 --- a/app/Http/Controllers/API/ServerController.php +++ b/app/Http/Controllers/API/ServerController.php @@ -72,10 +72,10 @@ class ServerController extends BaseController public function create(Request $request) { try { - $server = new ServerRepository; - $new = $server->create($request->all()); + $repo = new ServerRepository; + $server = $repo->create($request->all()); - return ['id' => $new]; + return ['id' => $server->id]; } catch (DisplayValidationException $ex) { throw new ResourceException('A validation error occured.', json_decode($ex->getMessage(), true)); } catch (DisplayException $ex) { @@ -101,55 +101,38 @@ class ServerController extends BaseController */ public function view(Request $request, $id) { - $query = Models\Server::where('id', $id); + $server = Models\Server::with('node', 'allocations', 'pack')->where('id', $id)->first(); + if (! $server) { + throw new NotFoundHttpException('No server by that ID was found.'); + } if (! is_null($request->input('fields'))) { - foreach (explode(',', $request->input('fields')) as $field) { - if (! empty($field)) { - $query->addSelect($field); - } + $fields = explode(',', $request->input('fields')); + if (! empty($fields) && is_array($fields)) { + return collect($server)->only($fields); } } - try { - if (! $query->first()) { - throw new NotFoundHttpException('No server by that ID was found.'); - } + if ($request->input('daemon') === 'true') { + try { + $response = $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('GET', '/servers'); - // Requested Daemon Stats - $server = $query->first(); - if ($request->input('daemon') === 'true') { - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($node->id); - - $response = $client->request('GET', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - ], - ]); - - // Only return the daemon token if the request is using HTTPS - if ($request->secure()) { - $server->daemon_token = $server->daemonSecret; - } $server->daemon = json_decode($response->getBody())->{$server->uuid}; - - return $server->toArray(); + } catch (\GuzzleHttp\Exception\TransferException $ex) { + // Couldn't hit the daemon, return what we have though. + $server->daemon = [ + 'error' => 'There was an error encountered while attempting to connect to the remote daemon.', + ]; } - - return $server->toArray(); - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\GuzzleHttp\Exception\TransferException $ex) { - // Couldn't hit the daemon, return what we have though. - $server->daemon = [ - 'error' => 'There was an error encountered while attempting to connect to the remote daemon.', - ]; - - return $server->toArray(); - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); } + + $server->allocations->transform(function ($item) { + return collect($item)->except(['created_at', 'updated_at']); + }); + + return $server->toArray(); } /** @@ -176,7 +159,9 @@ class ServerController extends BaseController { try { $server = new ServerRepository; - $server->updateDetails($id, $request->all()); + $server->updateDetails($id, $request->only([ + 'owner', 'name', 'reset_token', + ])); return Models\Server::findOrFail($id); } catch (DisplayValidationException $ex) { @@ -221,7 +206,10 @@ class ServerController extends BaseController { try { $server = new ServerRepository; - $server->changeBuild($id, $request->all()); + $server->changeBuild($id, $request->only([ + 'default', 'add_additional', 'remove_additional', + 'memory', 'swap', 'io', 'cpu', 'disk', + ])); return Models\Server::findOrFail($id); } catch (DisplayValidationException $ex) { diff --git a/app/Http/Controllers/API/ServiceController.php b/app/Http/Controllers/API/ServiceController.php index aa8b4ac29..b1c600e27 100755 --- a/app/Http/Controllers/API/ServiceController.php +++ b/app/Http/Controllers/API/ServiceController.php @@ -45,17 +45,11 @@ class ServiceController extends BaseController public function view(Request $request, $id) { - $service = Models\Service::find($id); + $service = Models\Service::with('options.variables', 'options.packs')->find($id); if (! $service) { throw new NotFoundHttpException('No service by that ID was found.'); } - return [ - 'service' => $service, - 'options' => Models\ServiceOptions::select('id', 'name', 'description', 'tag', 'docker_image') - ->where('parent_service', $service->id) - ->with('variables') - ->get(), - ]; + return $service->toArray(); } } diff --git a/app/Http/Controllers/API/User/InfoController.php b/app/Http/Controllers/API/User/InfoController.php index 00923b866..a22d1e114 100644 --- a/app/Http/Controllers/API/User/InfoController.php +++ b/app/Http/Controllers/API/User/InfoController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\API\User; -use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Http\Controllers\API\BaseController; @@ -32,19 +31,16 @@ class InfoController extends BaseController { public function me(Request $request) { - return Models\Server::getUserServers()->map(function ($server) { + return $request->user()->serverAccessCollection()->load('allocation', 'option')->map(function ($server) { return [ 'id' => $server->uuidShort, 'uuid' => $server->uuid, 'name' => $server->name, - 'node' => $server->nodeName, - 'ip' => [ - 'set' => $server->ip, - 'alias' => $server->ip_alias, - ], - 'port' => $server->port, - 'service' => $server->a_serviceName, - 'option' => $server->a_serviceOptionName, + 'node' => $server->node->name, + 'ip' => $server->allocation->alias, + 'port' => $server->allocation->port, + 'service' => $server->service->name, + 'option' => $server->option->name, ]; })->all(); } diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php index c63a482fa..465c7a345 100644 --- a/app/Http/Controllers/API/User/ServerController.php +++ b/app/Http/Controllers/API/User/ServerController.php @@ -25,7 +25,6 @@ namespace Pterodactyl\Http\Controllers\API\User; use Log; -use Auth; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Http\Controllers\API\BaseController; @@ -34,42 +33,28 @@ class ServerController extends BaseController { public function info(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($node->id); + $server = Models\Server::byUuid($uuid)->load('allocations'); try { - $response = $client->request('GET', '/server', [ - 'headers' => [ - 'X-Access-Token' => $server->daemonSecret, - 'X-Access-Server' => $server->uuid, - ], - ]); + $response = $server->guzzleClient()->request('GET', '/server'); $json = json_decode($response->getBody()); $daemon = [ 'status' => $json->status, 'stats' => $json->proc, - 'query' => $json->query, ]; } catch (\Exception $ex) { $daemon = [ - 'error' => 'An error was encountered while trying to connect to the daemon to collece information. It might be offline.', + 'error' => 'An error was encountered while trying to connect to the daemon to collect information. It might be offline.', ]; Log::error($ex); } - $allocations = Models\Allocation::select('id', 'ip', 'port', 'ip_alias as alias')->where('assigned_to', $server->id)->get(); - foreach ($allocations as &$allocation) { - $allocation->default = ($allocation->id === $server->allocation); - unset($allocation->id); - } - return [ 'uuidShort' => $server->uuidShort, 'uuid' => $server->uuid, 'name' => $server->name, - 'node' => $node->name, + 'node' => $server->node->name, 'limits' => [ 'memory' => $server->memory, 'swap' => $server->swap, @@ -78,12 +63,18 @@ class ServerController extends BaseController 'cpu' => $server->cpu, 'oom_disabled' => (bool) $server->oom_disabled, ], - 'allocations' => $allocations, + 'allocations' => $server->allocations->map(function ($item) use ($server) { + return [ + 'ip' => $item->alias, + 'port' => $item->port, + 'default' => ($item->id === $server->allocation_id), + ]; + }), 'sftp' => [ - 'username' => (Auth::user()->can('view-sftp', $server)) ? $server->username : null, + 'username' => ($request->user()->can('view-sftp', $server)) ? $server->username : null, ], 'daemon' => [ - 'token' => ($request->secure()) ? $server->daemonSecret : false, + 'token' => $server->daemonSecret, 'response' => $daemon, ], ]; @@ -91,16 +82,10 @@ class ServerController extends BaseController public function power(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $client = Models\Node::guzzleRequest($server->node); - + $server = Models\Server::byUuid($uuid); Auth::user()->can('power-' . $request->input('action'), $server); - $res = $client->request('PUT', '/server/power', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->daemonSecret, - ], + $res = $server->guzzleClient()->request('PUT', '/server/power', [ 'exceptions' => false, 'json' => [ 'action' => $request->input('action'), diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php index a330ef61c..4121f64ef 100755 --- a/app/Http/Controllers/API/UserController.php +++ b/app/Http/Controllers/API/UserController.php @@ -31,7 +31,6 @@ use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\UserRepository; use Pterodactyl\Exceptions\DisplayValidationException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; /** @@ -75,31 +74,27 @@ class UserController extends BaseController */ public function view(Request $request, $id) { - $query = Models\User::where((is_numeric($id) ? 'id' : 'email'), $id); + $user = Models\User::with('servers')->where((is_numeric($id) ? 'id' : 'email'), $id)->first(); + if (! $user->first()) { + throw new NotFoundHttpException('No user by that ID was found.'); + } + + $user->servers->transform(function ($item) { + return collect($item)->only([ + 'id', 'node_id', 'uuidShort', + 'uuid', 'name', 'suspended', + 'owner_id', + ]); + }); if (! is_null($request->input('fields'))) { - foreach (explode(',', $request->input('fields')) as $field) { - if (! empty($field)) { - $query->addSelect($field); - } + $fields = explode(',', $request->input('fields')); + if (! empty($fields) && is_array($fields)) { + return collect($user)->only($fields); } } - try { - if (! $query->first()) { - throw new NotFoundHttpException('No user by that ID was found.'); - } - - $user = $query->first(); - $userArray = $user->toArray(); - $userArray['servers'] = Models\Server::select('id', 'uuid', 'node', 'suspended')->where('owner', $user->id)->get(); - - return $userArray; - } catch (NotFoundHttpException $ex) { - throw $ex; - } catch (\Exception $ex) { - throw new BadRequestHttpException('There was an issue with the fields passed in the request.'); - } + return $user->toArray(); } /** @@ -123,7 +118,9 @@ class UserController extends BaseController try { $user = new UserRepository; $create = $user->create($request->only([ - 'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id', + 'email', 'username', 'name_first', + 'name_last', 'password', + 'root_admin', 'custom_id', ])); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); @@ -160,7 +157,9 @@ class UserController extends BaseController try { $user = new UserRepository; $user->update($id, $request->only([ - 'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language', + 'username', 'email', 'name_first', + 'name_last', 'password', + 'root_admin', 'language', ])); return Models\User::findOrFail($id); diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 4a7fc3631..7014c321b 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; use Log; use Alert; use Pterodactyl\Models; @@ -47,30 +46,15 @@ class DatabaseController extends Controller public function getIndex(Request $request) { return view('admin.databases.index', [ - 'databases' => Models\Database::select( - 'databases.*', - 'database_servers.host as a_host', - 'database_servers.port as a_port', - 'servers.id as a_serverId', - 'servers.name as a_serverName' - )->join('database_servers', 'database_servers.id', '=', 'databases.db_server') - ->join('servers', 'databases.server_id', '=', 'servers.id') - ->paginate(20), - 'dbh' => Models\DatabaseServer::select( - 'database_servers.*', - 'nodes.name as a_linkedNode', - DB::raw('(SELECT COUNT(*) FROM `databases` WHERE `databases`.`db_server` = database_servers.id) as c_databases') - )->leftJoin('nodes', 'nodes.id', '=', 'database_servers.linked_node') - ->paginate(20), + 'databases' => Models\Database::with('server')->paginate(50), + 'hosts' => Models\DatabaseServer::withCount('databases')->with('node')->paginate(20), ]); } public function getNew(Request $request) { return view('admin.databases.new', [ - 'nodes' => Models\Node::select('nodes.id', 'nodes.name', 'locations.long as a_location') - ->join('locations', 'locations.id', '=', 'nodes.location') - ->get(), + 'nodes' => Models\Node::all()->load('location'), ]); } @@ -78,15 +62,17 @@ class DatabaseController extends Controller { try { $repo = new DatabaseRepository; - $repo->add($request->except([ - '_token', + $repo->add($request->only([ + 'name', + 'host', + 'port', + 'username', + 'password', + 'linked_node', ])); - Alert::success('Successfully added a new database server to the system.')->flash(); - return redirect()->route('admin.databases', [ - 'tab' => 'tab_dbservers', - ]); + return redirect()->route('admin.databases', ['tab' => 'tab_dbservers']); } catch (DisplayValidationException $ex) { return redirect()->route('admin.databases.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (\Exception $ex) { diff --git a/app/Http/Controllers/Admin/LocationsController.php b/app/Http/Controllers/Admin/LocationsController.php index 889123b43..94c9ebf59 100644 --- a/app/Http/Controllers/Admin/LocationsController.php +++ b/app/Http/Controllers/Admin/LocationsController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; use Alert; use Pterodactyl\Models; use Illuminate\Http\Request; @@ -43,35 +42,21 @@ class LocationsController extends Controller public function getIndex(Request $request) { return view('admin.locations.index', [ - 'locations' => Models\Location::select( - 'locations.*', - DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'), - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount') - )->paginate(20), + 'locations' => Models\Location::withCount('nodes', 'servers')->paginate(20), ]); } public function deleteLocation(Request $request, $id) { - $model = Models\Location::select( - 'locations.id', - DB::raw('(SELECT COUNT(*) FROM nodes WHERE nodes.location = locations.id) as a_nodeCount'), - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node IN (SELECT nodes.id FROM nodes WHERE nodes.location = locations.id)) as a_serverCount') - )->where('id', $id)->first(); + $location = Models\Location::withCount('nodes')->findOrFail($id); - if (! $model) { + if ($location->nodes_count > 0) { return response()->json([ - 'error' => 'No location with that ID exists on the system.', - ], 404); - } - - if ($model->a_nodeCount > 0 || $model->a_serverCount > 0) { - return response()->json([ - 'error' => 'You cannot remove a location that is currently assigned to a node or server.', + 'error' => 'You cannot remove a location that is currently assigned to a node.', ], 422); } - $model->delete(); + $location->delete(); return response('', 204); } @@ -80,12 +65,12 @@ class LocationsController extends Controller { try { $location = new LocationRepository; - $location->edit($id, $request->all()); + $location->edit($id, $request->only(['long', 'short'])); return response('', 204); } catch (DisplayValidationException $ex) { return response()->json([ - 'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 10 characters with no spaces or special characters.', + 'error' => 'There was a validation error while processing this request. Location descriptions must be between 1 and 255 characters, and the location code must be between 1 and 20 characters with no spaces or special characters.', ], 422); } catch (\Exception $ex) { // This gets caught and processed into JSON anyways. @@ -97,9 +82,7 @@ class LocationsController extends Controller { try { $location = new LocationRepository; - $id = $location->create($request->except([ - '_token', - ])); + $location->create($request->only(['long', 'short'])); Alert::success('New location successfully added.')->flash(); return redirect()->route('admin.locations'); diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 594365f73..877c44995 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -48,17 +48,15 @@ class NodesController extends Controller public function getScript(Request $request, $id) { - return response()->view('admin.nodes.remote.deploy', ['node' => Models\Node::findOrFail($id)])->header('Content-Type', 'text/plain'); + return response()->view('admin.nodes.remote.deploy', [ + 'node' => Models\Node::findOrFail($id), + ])->header('Content-Type', 'text/plain'); } public function getIndex(Request $request) { return view('admin.nodes.index', [ - 'nodes' => Models\Node::select( - 'nodes.*', - 'locations.long as a_locationName', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node = nodes.id) as a_serverCount') - )->join('locations', 'nodes.location', '=', 'locations.id')->paginate(20), + 'nodes' => Models\Node::with('location')->withCount('servers')->paginate(20), ]); } @@ -78,15 +76,18 @@ class NodesController extends Controller public function postNew(Request $request) { try { - $node = new NodeRepository; - $new = $node->create($request->except([ - '_token', + $repo = new NodeRepository; + $node = $repo->create($request->only([ + 'name', 'location_id', 'public', + 'fqdn', 'scheme', 'memory', + 'memory_overallocate', 'disk', + 'disk_overallocate', 'daemonBase', + 'daemonSFTP', 'daemonListen', ])); - Alert::success('Successfully created new node. Before you can add any servers you need to first assign some IP addresses and ports.')->flash(); - Alert::info('To simplify the node setup you can generate a token on the configuration tab.')->flash(); + 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(); return redirect()->route('admin.nodes.view', [ - 'id' => $new, + 'id' => $node->id, 'tab' => 'tab_allocation', ]); } catch (DisplayValidationException $e) { @@ -103,26 +104,16 @@ class NodesController extends Controller public function getView(Request $request, $id) { - $node = Models\Node::findOrFail($id); + $node = Models\Node::with( + 'servers.user', 'servers.service', + 'servers.allocations', 'location' + )->findOrFail($id); + $node->setRelation('allocations', $node->allocations()->with('server')->paginate(40)); return view('admin.nodes.view', [ 'node' => $node, - 'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail', 'services.name as a_serviceName') - ->join('users', 'users.id', '=', 'servers.owner') - ->join('services', 'services.id', '=', 'servers.service') - ->where('node', $id)->paginate(10, ['*'], 'servers'), - 'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(), + 'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(), 'locations' => Models\Location::all(), - 'allocations' => Models\Allocation::select('allocations.*', 'servers.name as assigned_to_name') - ->where('allocations.node', $node->id) - ->leftJoin('servers', 'servers.id', '=', 'allocations.assigned_to') - ->orderBy('allocations.ip', 'asc') - ->orderBy('allocations.port', 'asc') - ->paginate(20, ['*'], 'allocations'), - 'allocation_ips' => Models\Allocation::select('id', 'ip') - ->where('node', $node->id) - ->groupBy('ip') - ->get(), ]); } @@ -130,8 +121,12 @@ class NodesController extends Controller { try { $node = new NodeRepository; - $node->update($id, $request->except([ - '_token', + $node->update($id, $request->only([ + 'name', 'location_id', 'public', + 'fqdn', 'scheme', 'memory', + 'memory_overallocate', 'disk', + 'disk_overallocate', 'upload_size', + 'daemonSFTP', 'daemonListen', 'reset_secret', ])); Alert::success('Successfully update this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); @@ -156,7 +151,7 @@ class NodesController extends Controller public function deallocateSingle(Request $request, $node, $allocation) { - $query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('id', $allocation)->delete(); + $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete(); if ((int) $query === 0) { return response()->json([ 'error' => 'Unable to find an allocation matching those details to delete.', @@ -168,7 +163,7 @@ class NodesController extends Controller public function deallocateBlock(Request $request, $node) { - $query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('ip', $request->input('ip'))->delete(); + $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete(); if ((int) $query === 0) { Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); @@ -204,7 +199,7 @@ class NodesController extends Controller public function getAllocationsJson(Request $request, $id) { - $allocations = Models\Allocation::select('ip')->where('node', $id)->groupBy('ip')->get(); + $allocations = Models\Allocation::select('ip')->where('node_id', $id)->groupBy('ip')->get(); return response()->json($allocations); } diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index abda49c8e..f4e577d0e 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; use Log; use Alert; use Storage; @@ -42,64 +41,29 @@ class PackController extends Controller // } - protected function formatServices() - { - $options = Models\ServiceOptions::select( - 'services.name AS p_service', - 'service_options.id', - 'service_options.name' - )->join('services', 'services.id', '=', 'service_options.parent_service')->get(); - - $array = []; - foreach ($options as &$option) { - if (! array_key_exists($option->p_service, $array)) { - $array[$option->p_service] = []; - } - - $array[$option->p_service] = array_merge($array[$option->p_service], [[ - 'id' => $option->id, - 'name' => $option->name, - ]]); - } - - return $array; - } - public function listAll(Request $request) { - return view('admin.services.packs.index', [ - 'services' => Models\Service::all(), - ]); + return view('admin.services.packs.index', ['services' => Models\Service::all()]); } public function listByOption(Request $request, $id) { - $option = Models\ServiceOptions::findOrFail($id); - return view('admin.services.packs.byoption', [ - 'packs' => Models\ServicePack::where('option', $option->id)->get(), - 'service' => Models\Service::findOrFail($option->parent_service), - 'option' => $option, + 'option' => Models\ServiceOption::with('service', 'packs')->findOrFail($id), ]); } public function listByService(Request $request, $id) { return view('admin.services.packs.byservice', [ - 'service' => Models\Service::findOrFail($id), - 'options' => Models\ServiceOptions::select( - 'service_options.id', - 'service_options.name', - DB::raw('(SELECT COUNT(id) FROM service_packs WHERE service_packs.option = service_options.id) AS p_count') - )->where('parent_service', $id)->get(), + 'service' => Models\Service::with('options', 'options.packs')->findOrFail($id), ]); } public function new(Request $request, $opt = null) { return view('admin.services.packs.new', [ - 'services' => $this->formatServices(), - 'packFor' => $opt, + 'services' => Models\Service::with('options')->get(), ]); } @@ -107,12 +71,14 @@ class PackController extends Controller { try { $repo = new Pack; - $id = $repo->create($request->except([ - '_token', + $pack = $repo->create($request->only([ + 'name', 'version', 'description', + 'option', 'selectable', 'visible', + 'file_upload', ])); Alert::success('Successfully created new service!')->flash(); - return redirect()->route('admin.services.packs.edit', $id)->withInput(); + return redirect()->route('admin.services.packs.edit', $pack->id)->withInput(); } catch (DisplayValidationException $ex) { return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { @@ -127,15 +93,12 @@ class PackController extends Controller public function edit(Request $request, $id) { - $pack = Models\ServicePack::findOrFail($id); - $option = Models\ServiceOptions::select('id', 'parent_service', 'name')->where('id', $pack->option)->first(); + $pack = Models\ServicePack::with('option.service')->findOrFail($id); return view('admin.services.packs.edit', [ 'pack' => $pack, - 'services' => $this->formatServices(), + 'services' => Models\Service::all()->load('options'), 'files' => Storage::files('packs/' . $pack->uuid), - 'service' => Models\Service::findOrFail($option->parent_service), - 'option' => $option, ]); } @@ -159,8 +122,9 @@ class PackController extends Controller } else { try { $repo = new Pack; - $repo->update($id, $request->except([ - '_token', + $repo->update($id, $request->only([ + 'name', 'version', 'description', + 'option', 'selectable', 'visible', ])); Alert::success('Service pack has been successfully updated.')->flash(); } catch (DisplayValidationException $ex) { @@ -183,14 +147,6 @@ class PackController extends Controller 'description' => $pack->dscription, 'selectable' => (bool) $pack->selectable, 'visible' => (bool) $pack->visible, - 'build' => [ - 'memory' => $pack->build_memory, - 'swap' => $pack->build_swap, - 'cpu' => $pack->build_cpu, - 'io' => $pack->build_io, - 'container' => $pack->build_container, - 'script' => $pack->build_script, - ], ]; $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); @@ -223,8 +179,7 @@ class PackController extends Controller public function uploadForm(Request $request, $for = null) { return view('admin.services.packs.upload', [ - 'services' => $this->formatServices(), - 'for' => $for, + 'services' => Models\Service::all()->load('options'), ]); } @@ -232,12 +187,10 @@ class PackController extends Controller { try { $repo = new Pack; - $id = $repo->createWithTemplate($request->except([ - '_token', - ])); + $pack = $repo->createWithTemplate($request->only(['option', 'file_upload'])); Alert::success('Successfully created new service!')->flash(); - return redirect()->route('admin.services.packs.edit', $id)->withInput(); + return redirect()->route('admin.services.packs.edit', $pack->id)->withInput(); } catch (DisplayValidationException $ex) { return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 9aece9c4e..017dc1fcc 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; use Log; use Alert; use Pterodactyl\Models; @@ -47,63 +46,8 @@ class ServersController extends Controller public function getIndex(Request $request) { - $query = Models\Server::withTrashed()->select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id'); - - if ($request->input('filter') && ! is_null($request->input('filter'))) { - preg_match_all('/[^\s"\']+|"([^"]*)"|\'([^\']*)\'/', urldecode($request->input('filter')), $matches); - foreach ($matches[0] as $match) { - $match = str_replace('"', '', $match); - if (strpos($match, ':')) { - list($field, $term) = explode(':', $match); - if ($field === 'node') { - $field = 'nodes.name'; - } elseif ($field === 'owner') { - $field = 'users.email'; - } elseif (! strpos($field, '.')) { - $field = 'servers.' . $field; - } - - $query->orWhere($field, 'LIKE', '%' . $term . '%'); - } else { - $query->where('servers.name', 'LIKE', '%' . $match . '%'); - $query->orWhere([ - ['servers.username', 'LIKE', '%' . $match . '%'], - ['users.email', 'LIKE', '%' . $match . '%'], - ['allocations.port', 'LIKE', '%' . $match . '%'], - ['allocations.ip', 'LIKE', '%' . $match . '%'], - ]); - } - } - } - - try { - $servers = $query->paginate(20); - } catch (\Exception $ex) { - Alert::warning('There was an error with the search parameters provided.'); - $servers = Models\Server::withTrashed()->select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id') - ->paginate(20); - } - return view('admin.servers.index', [ - 'servers' => $servers, + 'servers' => Models\Server::withTrashed()->with('node', 'user')->paginate(25), ]); } @@ -117,47 +61,21 @@ class ServersController extends Controller public function getView(Request $request, $id) { - $server = Models\Server::withTrashed()->select( - 'servers.*', - 'users.email as a_ownerEmail', - 'services.name as a_serviceName', - DB::raw('IFNULL(service_options.executable, services.executable) as a_serviceExecutable'), - 'service_options.docker_image', - 'service_options.name as a_servceOptionName', - 'allocations.ip', - 'allocations.port', - 'allocations.ip_alias' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('services', 'servers.service', '=', 'services.id') - ->join('service_options', 'servers.option', '=', 'service_options.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id') - ->where('servers.id', $id) - ->first(); + $server = Models\Server::withTrashed()->with( + 'user', 'option.variables', 'variables', + 'node.allocations', 'databases.host' + )->findOrFail($id); - if (! $server) { - return abort(404); - } + $server->option->variables->transform(function ($item, $key) use ($server) { + $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + + return $item; + }); return view('admin.servers.view', [ 'server' => $server, - 'node' => Models\Node::select( - 'nodes.*', - 'locations.long as a_locationName' - )->join('locations', 'nodes.location', '=', 'locations.id') - ->where('nodes.id', $server->node) - ->first(), - 'assigned' => Models\Allocation::where('assigned_to', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'unassigned' => Models\Allocation::where('node', $server->node)->whereNull('assigned_to')->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'startup' => Models\ServiceVariables::select('service_variables.*', 'server_variables.variable_value as a_serverValue') - ->join('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('service_variables.option_id', $server->option) - ->where('server_variables.server_id', $server->id) - ->get(), - 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') - ->where('server_id', $server->id) - ->join('database_servers', 'database_servers.id', '=', 'databases.db_server') - ->get(), + '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'), 'db_servers' => Models\DatabaseServer::all(), ]); } @@ -166,9 +84,9 @@ class ServersController extends Controller { try { $server = new ServerRepository; - $response = $server->create($request->all()); + $response = $server->create($request->except('_token')); - return redirect()->route('admin.servers.view', ['id' => $response]); + return redirect()->route('admin.servers.view', ['id' => $response->id]); } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { @@ -197,7 +115,7 @@ class ServersController extends Controller ], 500); } - return response()->json(Models\Node::select('id', 'name', 'public')->where('location', $request->input('location'))->get()); + return response()->json(Models\Node::select('id', 'name', 'public')->where('location_id', $request->input('location'))->get()); } /** @@ -214,7 +132,7 @@ class ServersController extends Controller ], 500); } - $ips = Models\Allocation::where('node', $request->input('node'))->whereNull('assigned_to')->get(); + $ips = Models\Allocation::where('node_id', $request->input('node'))->whereNull('server_id')->get(); $listing = []; foreach ($ips as &$ip) { @@ -234,7 +152,7 @@ class ServersController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\Contracts\View\View */ - public function postNewServerServiceOptions(Request $request) + public function postNewServerServiceOption(Request $request) { if (! $request->input('service')) { return response()->json([ @@ -244,7 +162,7 @@ class ServersController extends Controller $service = Models\Service::select('executable', 'startup')->where('id', $request->input('service'))->first(); - return response()->json(Models\ServiceOptions::select('id', 'name', 'docker_image')->where('parent_service', $request->input('service'))->orderBy('name', 'asc')->get()); + return response()->json(Models\ServiceOption::select('id', 'name', 'docker_image')->where('service_id', $request->input('service'))->orderBy('name', 'asc')->get()); } /** @@ -261,18 +179,15 @@ class ServersController extends Controller ], 500); } - $option = Models\ServiceOptions::select( - DB::raw('COALESCE(service_options.executable, services.executable) as executable'), - DB::raw('COALESCE(service_options.startup, services.startup) as startup') - )->leftJoin('services', 'services.id', '=', 'service_options.parent_service') - ->where('service_options.id', $request->input('option')) - ->first(); + $option = Models\ServiceOption::with('variables')->with(['packs' => function ($query) { + $query->where('selectable', true); + }])->findOrFail($request->input('option')); return response()->json([ - 'packs' => Models\ServicePack::select('id', 'name', 'version')->where('option', $request->input('option'))->where('selectable', true)->get(), - 'variables' => Models\ServiceVariables::where('option_id', $request->input('option'))->get(), - 'exec' => $option->executable, - 'startup' => $option->startup, + 'packs' => $option->packs, + 'variables' => $option->variables, + 'exec' => $option->display_executable, + 'startup' => $option->display_startup, ]); } @@ -309,9 +224,7 @@ class ServersController extends Controller { try { $server = new ServerRepository; - $server->updateContainer($id, [ - 'image' => $request->input('docker_image'), - ]); + $server->updateContainer($id, ['image' => $request->input('docker_image')]); Alert::success('Successfully updated this server\'s docker image.')->flash(); } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.view', [ @@ -333,17 +246,13 @@ class ServersController extends Controller public function postUpdateServerToggleBuild(Request $request, $id) { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); - $client = Models\Node::guzzleRequest($server->node); + $server = Models\Server::with('node')->findOrFail($id); try { - $res = $client->request('POST', '/server/rebuild', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], - ]); + $res = $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 (\GuzzleHttp\Exception\TransferException $ex) { Log::warning($ex); @@ -360,15 +269,11 @@ class ServersController extends Controller { try { $server = new ServerRepository; - $server->changeBuild($id, [ - 'default' => $request->input('default'), - 'add_additional' => $request->input('add_additional'), - 'remove_additional' => $request->input('remove_additional'), - 'memory' => $request->input('memory'), - 'swap' => $request->input('swap'), - 'io' => $request->input('io'), - 'cpu' => $request->input('cpu'), - ]); + $server->changeBuild($id, $request->only([ + 'default', 'add_additional', + 'remove_additional', 'memory', + 'swap', 'io', 'cpu', + ])); Alert::success('Server details were successfully updated.')->flash(); } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.view', [ @@ -458,8 +363,8 @@ class ServersController extends Controller { try { $repo = new DatabaseRepository; - $repo->create($id, $request->except([ - '_token', + $repo->create($id, $request->only([ + 'db_server', 'database', 'remote', ])); Alert::success('Added new database to this server.')->flash(); } catch (DisplayValidationException $ex) { diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 7a70c58da..e831c8683 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; use Log; use Alert; use Storage; @@ -45,10 +44,7 @@ class ServiceController extends Controller public function getIndex(Request $request) { return view('admin.services.index', [ - 'services' => Models\Service::select( - 'services.*', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.service = services.id) as c_servers') - )->get(), + 'services' => Models\Service::withCount('servers')->get(), ]); } @@ -61,12 +57,13 @@ class ServiceController extends Controller { try { $repo = new ServiceRepository\Service; - $id = $repo->create($request->except([ - '_token', + $service = $repo->create($request->only([ + 'name', 'description', 'file', + 'executable', 'startup', ])); Alert::success('Successfully created new service!')->flash(); - return redirect()->route('admin.services.service', $id); + return redirect()->route('admin.services.service', $service->id); } catch (DisplayValidationException $ex) { return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { @@ -82,11 +79,7 @@ class ServiceController extends Controller public function getService(Request $request, $service) { return view('admin.services.view', [ - 'service' => Models\Service::findOrFail($service), - 'options' => Models\ServiceOptions::select( - 'service_options.*', - DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.option = service_options.id) as c_servers') - )->where('parent_service', $service)->get(), + 'service' => Models\Service::with('options', 'options.servers')->findOrFail($service), ]); } @@ -94,8 +87,9 @@ class ServiceController extends Controller { try { $repo = new ServiceRepository\Service; - $repo->update($service, $request->except([ - '_token', + $repo->update($service, $request->only([ + 'name', 'description', 'file', + 'executable', 'startup', ])); Alert::success('Successfully updated this service.')->flash(); } catch (DisplayValidationException $ex) { @@ -130,25 +124,19 @@ class ServiceController extends Controller public function getOption(Request $request, $service, $option) { - $opt = Models\ServiceOptions::findOrFail($option); + $option = Models\ServiceOption::with('service', 'variables')->findOrFail($option); + $option->setRelation('servers', $option->servers()->with('user')->paginate(25)); - return view('admin.services.options.view', [ - 'service' => Models\Service::findOrFail($opt->parent_service), - 'option' => $opt, - 'variables' => Models\ServiceVariables::where('option_id', $option)->get(), - 'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail') - ->join('users', 'users.id', '=', 'servers.owner') - ->where('option', $option) - ->paginate(10), - ]); + return view('admin.services.options.view', ['option' => $option]); } public function postOption(Request $request, $service, $option) { try { $repo = new ServiceRepository\Option; - $repo->update($option, $request->except([ - '_token', + $repo->update($option, $request->only([ + 'name', 'description', 'tag', + 'executable', 'docker_image', 'startup', ])); Alert::success('Option settings successfully updated.')->flash(); } catch (DisplayValidationException $ex) { @@ -164,13 +152,12 @@ class ServiceController extends Controller public function deleteOption(Request $request, $service, $option) { try { - $service = Models\ServiceOptions::select('parent_service')->where('id', $option)->first(); $repo = new ServiceRepository\Option; $repo->delete($option); Alert::success('Successfully deleted that option.')->flash(); - return redirect()->route('admin.services.service', $service->parent_service); + return redirect()->route('admin.services.service', $service); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { @@ -218,8 +205,7 @@ class ServiceController extends Controller public function getNewVariable(Request $request, $service, $option) { return view('admin.services.options.variable', [ - 'service' => Models\Service::findOrFail($service), - 'option' => Models\ServiceOptions::where('parent_service', $service)->where('id', $option)->firstOrFail(), + 'option' => Models\ServiceOption::with('service')->findOrFail($option), ]); } @@ -227,8 +213,10 @@ class ServiceController extends Controller { try { $repo = new ServiceRepository\Variable; - $repo->create($option, $request->except([ - '_token', + $repo->create($option, $request->only([ + 'name', 'description', 'env_variable', + 'default_value', 'user_viewable', + 'user_editable', 'required', 'regex', ])); Alert::success('Successfully added new variable to this option.')->flash(); @@ -305,9 +293,7 @@ class ServiceController extends Controller { try { $repo = new ServiceRepository\Service; - $repo->updateFile($serviceId, $request->except([ - '_token', - ])); + $repo->updateFile($serviceId, $request->only(['file', 'contents'])); return response('', 204); } catch (DisplayException $ex) { diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 9a2c6fd04..8f90795c2 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -29,7 +29,6 @@ use Log; use Alert; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\UserRepository; @@ -45,35 +44,11 @@ class UserController extends Controller // } + // @TODO: implement nicolaslopezj/searchable to clean up this disaster. public function getIndex(Request $request) { - $query = User::select('users.*'); - if ($request->input('filter') && ! is_null($request->input('filter'))) { - preg_match_all('/[^\s"\']+|"([^"]*)"|\'([^\']*)\'/', urldecode($request->input('filter')), $matches); - foreach ($matches[0] as $match) { - $match = str_replace('"', '', $match); - if (strpos($match, ':')) { - list($field, $term) = explode(':', $match); - $query->orWhere($field, 'LIKE', '%' . $term . '%'); - } else { - $query->where('email', 'LIKE', '%' . $match . '%'); - $query->orWhere([ - ['uuid', 'LIKE', '%' . $match . '%'], - ['root_admin', 'LIKE', '%' . $match . '%'], - ]); - } - } - } - - try { - $users = $query->paginate(20); - } catch (\Exception $ex) { - Alert::warning('There was an error with the search parameters provided.'); - $users = User::all()->paginate(20); - } - return view('admin.users.index', [ - 'users' => $users, + 'users' => User::paginate(25), ]); } @@ -85,12 +60,7 @@ class UserController extends Controller public function getView(Request $request, $id) { return view('admin.users.view', [ - 'user' => User::findOrFail($id), - 'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location') - ->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->where('owner', $id) - ->get(), + 'user' => User::with('servers.node')->findOrFail($id), ]); } @@ -117,12 +87,8 @@ class UserController extends Controller try { $user = new UserRepository; $userid = $user->create($request->only([ - 'email', - 'password', - 'name_first', - 'name_last', - 'username', - 'root_admin', + 'email', 'password', 'name_first', + 'name_last', 'username', 'root_admin', ])); Alert::success('Account has been successfully created.')->flash(); @@ -142,12 +108,8 @@ class UserController extends Controller try { $repo = new UserRepository; $repo->update($user, $request->only([ - 'email', - 'password', - 'name_first', - 'name_last', - 'username', - 'root_admin', + 'email', 'password', 'name_first', + 'name_last', 'username', 'root_admin', ])); Alert::success('User account was successfully updated.')->flash(); } catch (DisplayValidationException $ex) { @@ -162,10 +124,6 @@ class UserController extends Controller public function getJson(Request $request) { - foreach (User::select('email')->get() as $user) { - $resp[] = $user->email; - } - - return $resp; + return User::select('email')->get()->pluck('email'); } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 9890077f1..d4d8c89c3 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -27,6 +27,7 @@ namespace Pterodactyl\Http\Controllers\Auth; use Auth; use Alert; +use Cache; use Illuminate\Http\Request; use Pterodactyl\Models\User; use PragmaRX\Google2FA\Google2FA; @@ -86,8 +87,11 @@ class LoginController extends Controller */ public function login(Request $request) { + // Check wether the user identifier is an email address or a username + $isEmail = str_contains($request->input('user'), '@'); + $this->validate($request, [ - 'email' => 'required|email', + 'user' => $isEmail ? 'required|email' : 'required|string', 'password' => 'required', ]); @@ -97,9 +101,9 @@ class LoginController extends Controller return $this->sendLockoutResponse($request); } - // Is the email & password valid? + // Is the user (email or username) & password valid? if (! Auth::once([ - 'email' => $request->input('email'), + $isEmail ? 'email' : 'username' => $request->input('user'), 'password' => $request->input('password'), ], $request->has('remember'))) { if (! $lockedOut) { @@ -110,33 +114,60 @@ class LoginController extends Controller } // Verify TOTP Token was Valid - if (Auth::user()->use_totp === 1) { - $G2FA = new Google2FA(); - if (is_null($request->input('totp_token')) || ! $G2FA->verifyKey(Auth::user()->totp_secret, $request->input('totp_token'))) { - if (! $lockedOut) { - $this->incrementLoginAttempts($request); - } + if (Auth::user()->use_totp) { + $verifyKey = str_random(64); + Cache::put($verifyKey, Auth::user()->id, 5); - Alert::danger(trans('auth.totp_failed'))->flash(); + return redirect()->route('auth.totp')->with('authentication_token', $verifyKey); + } else { + Auth::login(Auth::user(), $request->has('remember')); - return $this->sendFailedLoginResponse($request); - } + return $this->sendLoginResponse($request); + } + } + + public function totp(Request $request) + { + $verifyKey = $request->session()->get('authentication_token'); + + if (is_null($verifyKey) || Auth::user()) { + return redirect()->route('auth.login'); } - // Successfully Authenticated. - Auth::login(Auth::user(), $request->has('remember')); - - return $this->sendLoginResponse($request); + return view('auth.totp', [ + 'verify_key' => $verifyKey, + 'remember' => $request->has('remember'), + ]); } - /** - * Check if the provided user has TOTP enabled. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function checkTotp(Request $request) + public function totpCheckpoint(Request $request) { - return response()->json(User::select('id')->where('email', $request->input('email'))->where('use_totp', 1)->first()); + $G2FA = new Google2FA(); + + if (is_null($request->input('verify_token'))) { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.totp_failed'))->flash(); + + return redirect()->route('auth.login'); + } + + $user = User::where('id', Cache::pull($request->input('verify_token')))->first(); + if (! $user) { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.totp_failed'))->flash(); + + return redirect()->route('auth.login'); + } + + if (! is_null($request->input('2fa_token')) && $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 1)) { + Auth::login($user, $request->has('remember')); + + return redirect()->intended($this->redirectPath()); + } else { + $this->incrementLoginAttempts($request); + Alert::danger(trans('auth.2fa_failed'))->flash(); + + return redirect()->route('auth.login'); + } } } diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/APIController.php index 611638c95..68045a722 100644 --- a/app/Http/Controllers/Base/APIController.php +++ b/app/Http/Controllers/Base/APIController.php @@ -38,13 +38,8 @@ class APIController extends Controller { public function index(Request $request) { - $keys = Models\APIKey::where('user', $request->user()->id)->get(); - foreach ($keys as &$key) { - $key->permissions = Models\APIPermission::where('key_id', $key->id)->get(); - } - return view('base.api.index', [ - 'keys' => $keys, + 'keys' => Models\APIKey::where('user_id', $request->user()->id)->get(), ]); } @@ -57,8 +52,11 @@ class APIController extends Controller { try { $repo = new APIRepository($request->user()); - $secret = $repo->create($request->except(['_token'])); - Alert::success('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); + $secret = $repo->create($request->only([ + 'memo', 'allowed_ips', + 'adminPermissions', '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) { @@ -81,6 +79,8 @@ class APIController extends Controller 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/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 160bc67e6..f6abf4ea4 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -26,7 +26,6 @@ namespace Pterodactyl\Http\Controllers\Base; use Illuminate\Http\Request; -use Pterodactyl\Models\Server; use Pterodactyl\Http\Controllers\Controller; class IndexController extends Controller @@ -48,7 +47,7 @@ class IndexController extends Controller public function getIndex(Request $request) { return view('base.index', [ - 'servers' => Server::getUserServers(10), + 'servers' => $request->user()->serverAccessCollection(10), ]); } diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php index cf8bf2dc1..23b6f0d68 100644 --- a/app/Http/Controllers/Base/SecurityController.php +++ b/app/Http/Controllers/Base/SecurityController.php @@ -118,8 +118,7 @@ class SecurityController extends Controller public function revoke(Request $request, $id) { - $session = Session::where('id', $id)->where('user_id', $request->user()->id)->firstOrFail(); - $session->delete(); + Session::where('user_id', $request->user()->id)->findOrFail($id)->delete(); return redirect()->route('account.security'); } diff --git a/app/Http/Controllers/Daemon/PackController.php b/app/Http/Controllers/Daemon/PackController.php new file mode 100644 index 000000000..e96aa6ee5 --- /dev/null +++ b/app/Http/Controllers/Daemon/PackController.php @@ -0,0 +1,95 @@ +. + * + * 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 Storage; +use Pterodactyl\Models; +use Illuminate\Http\Request; +use Pterodactyl\Http\Controllers\Controller; + +class PackController extends Controller +{ + /** + * Controller Constructor. + */ + public function __construct() + { + // + } + + /** + * Pulls an install pack archive from the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function pull(Request $request, $uuid) + { + $pack = Models\ServicePack::where('uuid', $uuid)->first(); + + if (! $pack) { + return response()->json(['error' => 'No such pack.'], 404); + } + + if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { + return response()->json(['error' => 'There is no archive available for this pack.'], 503); + } + + return response()->download(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')); + } + + /** + * Returns the hash information for a pack. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function hash(Request $request, $uuid) + { + $pack = Models\ServicePack::where('uuid', $uuid)->first(); + + if (! $pack) { + return response()->json(['error' => 'No such pack.'], 404); + } + + if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { + return response()->json(['error' => 'There is no archive available for this pack.'], 503); + } + + return response()->json([ + 'archive.tar.gz' => sha1_file(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')), + ]); + } + + /** + * Pulls an update pack archive from the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function pullUpdate(Request $request) + { + } +} diff --git a/app/Http/Controllers/Remote/RemoteController.php b/app/Http/Controllers/Remote/RemoteController.php index b0aa0983e..23ae805b6 100644 --- a/app/Http/Controllers/Remote/RemoteController.php +++ b/app/Http/Controllers/Remote/RemoteController.php @@ -28,7 +28,6 @@ use Carbon\Carbon; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\NotificationService; class RemoteController extends Controller { @@ -42,7 +41,7 @@ class RemoteController extends Controller public function postDownload(Request $request) { - $download = Models\Download::where('token', $request->input('token', '00'))->first(); + $download = Models\Download::where('token', $request->input('token'))->first(); if (! $download) { return response()->json([ 'error' => 'An invalid request token was recieved with this request.', @@ -59,18 +58,17 @@ class RemoteController extends Controller public function postInstall(Request $request) { - $server = Models\Server::where('uuid', $request->input('server'))->first(); + $server = Models\Server::where('uuid', $request->input('server'))->with('node')->first(); if (! $server) { return response()->json([ 'error' => 'No server by that ID was found on the system.', ], 422); } - $node = Models\Node::findOrFail($server->node); $hmac = $request->input('signed'); $status = $request->input('installed'); - if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { + if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $server->node->daemonSecret, true)) { return response()->json([ 'error' => 'Signed HMAC was invalid.', ], 403); @@ -86,17 +84,15 @@ class RemoteController extends Controller public function event(Request $request) { - $server = Models\Server::where('uuid', $request->input('server'))->first(); + $server = Models\Server::where('uuid', $request->input('server'))->with('node')->first(); if (! $server) { return response()->json([ 'error' => 'No server by that ID was found on the system.', ], 422); } - $node = Models\Node::findOrFail($server->node); - $hmac = $request->input('signed'); - if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $node->daemonSecret, true)) { + if (base64_decode($hmac) !== hash_hmac('sha256', $server->uuid, $server->node->daemonSecret, true)) { return response()->json([ 'error' => 'Signed HMAC was invalid.', ], 403); @@ -130,7 +126,6 @@ class RemoteController extends Controller $token->delete(); // Manually as getConfigurationAsJson() returns it in correct format already - return response($node->getConfigurationAsJson(), 200) - ->header('Content-Type', 'application/json'); + return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json'); } } diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php index da4d058ac..8f3736da7 100644 --- a/app/Http/Controllers/Server/AjaxController.php +++ b/app/Http/Controllers/Server/AjaxController.php @@ -67,18 +67,22 @@ class AjaxController extends Controller */ public function getStatus(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); if (! $server) { return response()->json([], 404); } - $client = Models\Node::guzzleRequest($server->node); + if (! $server->installed) { + return response()->json(['status' => 20]); + } + + if ($server->suspended) { + return response()->json(['status' => 30]); + } try { - $res = $client->request('GET', '/server', [ - 'headers' => Models\Server::getGuzzleHeaders($uuid), - ]); + $res = $server->guzzleClient()->request('GET', '/server'); if ($res->getStatusCode() === 200) { return response()->json(json_decode($res->getBody())); } @@ -98,10 +102,10 @@ class AjaxController extends Controller */ public function postDirectoryList(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); + $server = Models\Server::byUuid($uuid); $this->authorize('list-files', $server); + $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); $prevDir = [ 'header' => ($this->directory !== '/') ? $this->directory : '', ]; @@ -149,7 +153,7 @@ class AjaxController extends Controller */ public function postSaveFile(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('save-files', $server); $controller = new Repositories\Daemon\FileRepository($uuid); @@ -175,17 +179,17 @@ class AjaxController extends Controller */ public function postSetPrimary(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('allocations'); $this->authorize('set-connection', $server); - if ((int) $request->input('allocation') === $server->allocation) { + if ((int) $request->input('allocation') === $server->allocation_id) { return response()->json([ 'error' => 'You are already using this as your default connection.', ], 409); } try { - $allocation = Models\Allocation::where('id', $request->input('allocation'))->where('assigned_to', $server->id)->first(); + $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.', @@ -217,10 +221,10 @@ class AjaxController extends Controller public function postResetDatabasePassword(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); - $database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail(); - + $server = Models\Server::byUuid($uuid); $this->authorize('reset-db-password', $server); + + $database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail(); try { $repo = new Repositories\DatabaseRepository; $password = str_random(16); diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index 593049ca1..07dee5439 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -28,7 +28,6 @@ use DB; use Log; use Uuid; use Alert; -use Javascript; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -55,14 +54,11 @@ class ServerController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\Contracts\View\View */ - public function getIndex(Request $request) + public function getIndex(Request $request, $uuid) { - $server = Models\Server::getByUUID($request->route()->server); - $node = Models\Node::find($server->node); + $server = Models\Server::byUuid($uuid); - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), + $server->js([ 'meta' => [ 'saveFile' => route('server.files.save', $server->uuidShort), 'csrfToken' => csrf_token(), @@ -71,7 +67,7 @@ class ServerController extends Controller return view('server.index', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, ]); } @@ -83,14 +79,10 @@ class ServerController extends Controller */ public function getFiles(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('list-files', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only('uuid', 'uuidShort', 'daemonSecret'), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), + $server->js([ 'meta' => [ 'directoryList' => route('server.files.directory-list', $server->uuidShort), 'csrftoken' => csrf_token(), @@ -108,7 +100,7 @@ class ServerController extends Controller return view('server.files.index', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, ]); } @@ -120,18 +112,14 @@ class ServerController extends Controller */ public function getAddFile(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('add-files', $server); - $node = Models\Node::find($server->node); - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.files.add', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', ]); } @@ -146,9 +134,8 @@ class ServerController extends Controller */ public function getEditFile(Request $request, $uuid, $file) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-files', $server); - $node = Models\Node::find($server->node); $fileInfo = (object) pathinfo($file); $controller = new FileRepository($uuid); @@ -166,15 +153,13 @@ class ServerController extends Controller return redirect()->route('server.files.index', $uuid); } - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), + $server->js([ 'stat' => $fileContent['stat'], ]); return view('server.files.edit', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, 'file' => $file, 'stat' => $fileContent['stat'], 'contents' => $fileContent['file']->content, @@ -192,9 +177,7 @@ class ServerController extends Controller */ public function getDownloadFile(Request $request, $uuid, $file) { - $server = Models\Server::getByUUID($uuid); - $node = Models\Node::find($server->node); - + $server = Models\Server::byUuid($uuid); $this->authorize('download-files', $server); $download = new Models\Download; @@ -205,69 +188,65 @@ class ServerController extends Controller $download->save(); - return redirect($node->scheme . '://' . $node->fqdn . ':' . $node->daemonListen . '/server/file/download/' . $download->token); + return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $download->token); } public function getAllocation(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('view-allocation', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.settings.allocation', [ - 'server' => $server, - 'allocations' => Models\Allocation::where('assigned_to', $server->id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), - 'node' => $node, + 'server' => $server->load(['allocations' => function ($query) { + $query->orderBy('ip', 'asc'); + $query->orderBy('port', 'asc'); + }]), + 'node' => $server->node, ]); } public function getStartup(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); + $server->load(['allocations' => function ($query) use ($server) { + $query->where('id', $server->allocation_id); + }]); $this->authorize('view-startup', $server); - $node = Models\Node::find($server->node); - $allocation = Models\Allocation::findOrFail($server->allocation); - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); - - $variables = Models\ServiceVariables::select( + $variables = Models\ServiceVariable::select( 'service_variables.*', DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_serverValue') )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('service_variables.option_id', $server->option) + ->where('service_variables.option_id', $server->option_id) ->where('server_variables.server_id', $server->id) ->get(); $service = Models\Service::select( DB::raw('IFNULL(service_options.executable, services.executable) as executable') - )->leftJoin('service_options', 'service_options.parent_service', '=', 'services.id') - ->where('service_options.id', $server->option) - ->where('services.id', $server->service) + )->leftJoin('service_options', 'service_options.service_id', '=', 'services.id') + ->where('service_options.id', $server->option_id) + ->where('services.id', $server->service_id) ->first(); - $serverVariables = [ + $allocation = $server->allocations->pop(); + $ServerVariable = [ '{{SERVER_MEMORY}}' => $server->memory, '{{SERVER_IP}}' => $allocation->ip, '{{SERVER_PORT}}' => $allocation->port, ]; - $processed = str_replace(array_keys($serverVariables), array_values($serverVariables), $server->startup); + $processed = str_replace(array_keys($ServerVariable), array_values($ServerVariable), $server->startup); foreach ($variables as &$variable) { - $replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '**'; + $replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '[hidden]'; $processed = str_replace('{{' . $variable->env_variable . '}}', $replace, $processed); } + $server->js(); + return view('server.settings.startup', [ 'server' => $server, - 'node' => Models\Node::find($server->node), + 'node' => $server->node, 'variables' => $variables->where('user_viewable', 1), 'service' => $service, 'processedStartup' => $processed, @@ -276,18 +255,13 @@ class ServerController extends Controller public function getDatabases(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('view-databases', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.settings.databases', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, 'databases' => Models\Database::select('databases.*', 'database_servers.host as a_host', 'database_servers.port as a_port') ->where('server_id', $server->id) ->join('database_servers', 'database_servers.id', '=', 'databases.db_server') @@ -297,24 +271,19 @@ class ServerController extends Controller public function getSFTP(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('view-sftp', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.settings.sftp', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, ]); } public function postSettingsSFTP(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('reset-sftp', $server); try { @@ -335,7 +304,7 @@ class ServerController extends Controller public function postSettingsStartup(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-startup', $server); try { @@ -351,9 +320,6 @@ class ServerController extends Controller Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); } - return redirect()->route('server.settings', [ - 'uuid' => $uuid, - 'tab' => 'tab_startup', - ]); + return redirect()->route('server.settings.startup', $uuid); } } diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index a8761c717..a3ae8e3ba 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -24,11 +24,9 @@ namespace Pterodactyl\Http\Controllers\Server; -use DB; use Log; use Auth; use Alert; -use Javascript; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -50,73 +48,47 @@ class SubuserController extends Controller public function getIndex(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('subusers.user'); $this->authorize('list-subusers', $server); - $node = Models\Node::find($server->node); - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.users.index', [ 'server' => $server, - 'node' => $node, - 'subusers' => Models\Subuser::select('subusers.*', 'users.email', 'users.username', 'users.use_totp') - ->join('users', 'users.id', '=', 'subusers.user_id') - ->where('server_id', $server->id) - ->get(), + 'node' => $server->node, + 'subusers' => $server->subusers, ]); } public function getView(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('node'); $this->authorize('view-subuser', $server); - $node = Models\Node::find($server->node); - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $subuser = Models\Subuser::with('permissions', 'user') + ->where('server_id', $server->id)->findOrFail($id); - $subuser = Models\Subuser::select('subusers.*', 'users.email as a_userEmail') - ->join('users', 'users.id', '=', 'subusers.user_id') - ->where(DB::raw('md5(subusers.id)'), $id)->where('subusers.server_id', $server->id) - ->first(); - - if (! $subuser) { - abort(404); - } - - $permissions = []; - $modelPermissions = Models\Permission::select('permission') - ->where('user_id', $subuser->user_id)->where('server_id', $server->id) - ->get(); - - foreach ($modelPermissions as &$perm) { - $permissions[$perm->permission] = true; - } + $server->js(); return view('server.users.view', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, 'subuser' => $subuser, - 'permissions' => $permissions, + 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) { + return [$item->permission => true]; + }), ]); } public function postView(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('edit-subuser', $server); - $subuser = Models\Subuser::where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); + $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); try { - if (! $subuser) { - throw new DisplayException('Unable to locate a subuser by that ID.'); - } elseif ($subuser->user_id === Auth::user()->id) { + if ($subuser->user_id === Auth::user()->id) { throw new DisplayException('You are not authorized to edit you own account.'); } @@ -148,36 +120,31 @@ class SubuserController extends Controller public function getNew(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-subuser', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.users.new', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, ]); } public function postNew(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-subuser', $server); try { $repo = new SubuserRepository; - $id = $repo->create($server->id, $request->except([ - '_token', + $subuser = $repo->create($server->id, $request->only([ + 'permissions', 'email', ])); Alert::success('Successfully created new subuser.')->flash(); return redirect()->route('server.subusers.view', [ 'uuid' => $uuid, - 'id' => md5($id), + 'id' => $subuser->id, ]); } catch (DisplayValidationException $ex) { return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); @@ -193,14 +160,11 @@ class SubuserController extends Controller public function deleteSubuser(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('delete-subuser', $server); try { - $subuser = Models\Subuser::select('id')->where(DB::raw('md5(id)'), $id)->where('server_id', $server->id)->first(); - if (! $subuser) { - throw new DisplayException('No subuser by that ID was found on the system.'); - } + $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); $repo = new SubuserRepository; $repo->delete($subuser->id); diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php index 8c49ad6bc..643e70a45 100644 --- a/app/Http/Controllers/Server/TaskController.php +++ b/app/Http/Controllers/Server/TaskController.php @@ -26,7 +26,6 @@ namespace Pterodactyl\Http\Controllers\Server; use Log; use Alert; -use Javascript; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Repositories; @@ -43,19 +42,14 @@ class TaskController extends Controller public function getIndex(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('tasks'); $this->authorize('list-tasks', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.tasks.index', [ 'server' => $server, - 'node' => $node, - 'tasks' => Models\Task::where('server', $server->id)->get(), + 'node' => $server->node, + 'tasks' => $server->tasks, 'actions' => [ 'command' => trans('server.tasks.actions.command'), 'power' => trans('server.tasks.actions.power'), @@ -65,24 +59,19 @@ class TaskController extends Controller public function getNew(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-task', $server); - $node = Models\Node::find($server->node); - - Javascript::put([ - 'server' => collect($server->makeVisible('daemonSecret'))->only(['uuid', 'uuidShort', 'daemonSecret', 'username']), - 'node' => collect($node)->only('fqdn', 'scheme', 'daemonListen'), - ]); + $server->js(); return view('server.tasks.new', [ 'server' => $server, - 'node' => $node, + 'node' => $server->node, ]); } public function postNew(Request $request, $uuid) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid); $this->authorize('create-task', $server); try { @@ -106,12 +95,11 @@ class TaskController extends Controller public function deleteTask(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('tasks'); $this->authorize('delete-task', $server); - $task = Models\Task::findOrFail($id); - - if (! $task || $server->id !== $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); @@ -133,12 +121,11 @@ class TaskController extends Controller public function toggleTask(Request $request, $uuid, $id) { - $server = Models\Server::getByUUID($uuid); + $server = Models\Server::byUuid($uuid)->load('tasks'); $this->authorize('toggle-task', $server); - $task = Models\Task::findOrFail($id); - - if (! $task || $server->id !== $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); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 9e8d9f816..971835dac 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -17,7 +17,9 @@ class Kernel extends HttpKernel \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Pterodactyl\Http\Middleware\LanguageMiddleware::class, + \Fideloper\Proxy\TrustProxies::class, ]; /** @@ -51,6 +53,7 @@ class Kernel extends HttpKernel '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, diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php index a1c203c9d..25bf891ba 100755 --- a/app/Http/Middleware/APISecretToken.php +++ b/app/Http/Middleware/APISecretToken.php @@ -121,7 +121,7 @@ class APISecretToken extends Authorization // Log the Route Access APILogService::log($request, null, true); - return Auth::loginUsingId($key->user); + return Auth::loginUsingId($key->user_id); } protected function _generateHMAC($body, $key) diff --git a/app/Http/Middleware/CheckServer.php b/app/Http/Middleware/CheckServer.php index cd83bd9d1..dba9395ac 100644 --- a/app/Http/Middleware/CheckServer.php +++ b/app/Http/Middleware/CheckServer.php @@ -26,6 +26,7 @@ namespace Pterodactyl\Http\Middleware; use Auth; use Closure; +use Illuminate\Http\Request; use Pterodactyl\Models\Server; class CheckServer @@ -37,22 +38,22 @@ class CheckServer * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { if (! Auth::user()) { return redirect()->guest('auth/login'); } - $server = Server::getByUUID($request->route()->server); + $server = Server::byUuid($request->route()->server); if (! $server) { return response()->view('errors.404', [], 404); } - if ($server->suspended === 1) { + if ($server->suspended) { return response()->view('errors.suspended', [], 403); } - if ($server->installed !== 1) { + if (! $server->installed) { return response()->view('errors.installing', [], 403); } diff --git a/app/Listeners/DeleteServerListener.php b/app/Http/Middleware/DaemonAuthenticate.php similarity index 60% rename from app/Listeners/DeleteServerListener.php rename to app/Http/Middleware/DaemonAuthenticate.php index 616f4c141..73cb029d4 100644 --- a/app/Listeners/DeleteServerListener.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -22,41 +22,50 @@ * SOFTWARE. */ -namespace Pterodactyl\Listeners; +namespace Pterodactyl\Http\Middleware; -use Carbon; -use Pterodactyl\Jobs\DeleteServer; -use Pterodactyl\Jobs\SuspendServer; -use Pterodactyl\Events\ServerDeleted; -use Illuminate\Foundation\Bus\DispatchesJobs; +use Closure; +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Auth\Guard; -class DeleteServerListener +class DaemonAuthenticate { - use DispatchesJobs; + /** + * The Guard implementation. + * + * @var Guard + */ + protected $auth; /** - * Create the event listener. + * Create a new filter instance. * + * @param Guard $auth * @return void */ - public function __construct() + public function __construct(Guard $auth) { - // + $this->auth = $auth; } /** - * Handle the event. + * Handle an incoming request. * - * @param DeleteServerEvent $event - * @return void + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed */ - public function handle(ServerDeleted $event) + public function handle($request, Closure $next) { - $this->dispatch((new SuspendServer($event->server))->onQueue(env('QUEUE_HIGH', 'high'))); - $this->dispatch( - (new DeleteServer($event->server)) - ->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10))) - ->onQueue(env('QUEUE_STANDARD', 'standard')) - ); + if (! $request->header('X-Access-Node')) { + return abort(403); + } + + $node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first(); + if (! $node) { + return abort(404); + } + + return $next($request); } } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index fe23f9e99..08d960353 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -13,6 +13,7 @@ class VerifyCsrfToken extends BaseVerifier */ protected $except = [ 'remote/*', + 'daemon/*', 'api/*', ]; } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index e3a81cb66..127055daa 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -144,7 +144,7 @@ class AdminRoutes ]); $router->post('/new/service-options', [ - 'uses' => 'Admin\ServersController@postNewServerServiceOptions', + 'uses' => 'Admin\ServersController@postNewServerServiceOption', ]); $router->post('/new/option-details', [ diff --git a/app/Http/Routes/AuthRoutes.php b/app/Http/Routes/AuthRoutes.php index 491bebe41..77968321c 100644 --- a/app/Http/Routes/AuthRoutes.php +++ b/app/Http/Routes/AuthRoutes.php @@ -51,9 +51,13 @@ class AuthRoutes 'uses' => 'Auth\LoginController@login', ]); - // Determine if we need to ask for a TOTP Token + $router->get('login/totp', [ + 'as' => 'auth.totp', + 'uses' => 'Auth\LoginController@totp', + ]); + $router->post('login/totp', [ - 'uses' => 'Auth\LoginController@checkTotp', + 'uses' => 'Auth\LoginController@totpCheckpoint', ]); // Show Password Reset Form diff --git a/app/Http/Routes/BaseRoutes.php b/app/Http/Routes/BaseRoutes.php index 41bb93b53..40e86f8a7 100644 --- a/app/Http/Routes/BaseRoutes.php +++ b/app/Http/Routes/BaseRoutes.php @@ -89,6 +89,7 @@ class BaseRoutes ]); $router->delete('/revoke/{key}', [ + 'as' => 'account.api.revoke', 'uses' => 'Base\APIController@revoke', ]); }); diff --git a/app/Http/Routes/DaemonRoutes.php b/app/Http/Routes/DaemonRoutes.php index b993e3759..7367fae5f 100644 --- a/app/Http/Routes/DaemonRoutes.php +++ b/app/Http/Routes/DaemonRoutes.php @@ -30,7 +30,7 @@ class DaemonRoutes { public function map(Router $router) { - $router->group(['prefix' => 'daemon'], function () use ($router) { + $router->group(['prefix' => 'daemon', 'middleware' => 'daemon'], function () use ($router) { $router->get('services', [ 'as' => 'daemon.services', 'uses' => 'Daemon\ServiceController@list', @@ -40,6 +40,15 @@ class DaemonRoutes 'as' => 'remote.install', 'uses' => 'Daemon\ServiceController@pull', ]); + + $router->get('packs/pull/{uuid}', [ + 'as' => 'daemon.pack.pull', + 'uses' => 'Daemon\PackController@pull', + ]); + $router->get('packs/pull/{uuid}/hash', [ + 'as' => 'daemon.pack.hash', + 'uses' => 'Daemon\PackController@hash', + ]); }); } } diff --git a/app/Http/Routes/RemoteRoutes.php b/app/Http/Routes/RemoteRoutes.php index ba8f47f9b..9ece1fb2e 100644 --- a/app/Http/Routes/RemoteRoutes.php +++ b/app/Http/Routes/RemoteRoutes.php @@ -42,11 +42,6 @@ class RemoteRoutes 'uses' => 'Remote\RemoteController@postInstall', ]); - $router->post('event', [ - 'as' => 'remote.event', - 'uses' => 'Remote\RemoteController@event', - ]); - $router->get('configuration/{token}', [ 'as' => 'remote.configuration', 'uses' => 'Remote\RemoteController@getConfiguration', diff --git a/app/Http/Routes/ServerRoutes.php b/app/Http/Routes/ServerRoutes.php index 079bbf449..36c95827b 100644 --- a/app/Http/Routes/ServerRoutes.php +++ b/app/Http/Routes/ServerRoutes.php @@ -30,6 +30,13 @@ class ServerRoutes { public function map(Router $router) { + // Returns Server Status + $router->get('/server/{server}/ajax/status', [ + 'middleware' => ['auth', 'csrf'], + 'as' => 'server.ajax.status', + 'uses' => 'Server\AjaxController@getStatus', + ]); + $router->group([ 'prefix' => 'server/{server}', 'middleware' => [ @@ -45,12 +52,6 @@ class ServerRoutes 'uses' => 'Server\ServerController@getIndex', ]); - // Settings - $router->get('/settings', [ - 'as' => 'server.settings', - 'uses' => 'Server\ServerController@getSettings', - ]); - $router->get('/settings/databases', [ 'as' => 'server.settings.databases', 'uses' => 'Server\ServerController@getDatabases', @@ -135,6 +136,7 @@ class ServerRoutes ]); $router->delete('users/delete/{id}', [ + 'as' => 'server.subusers.delete', 'uses' => 'Server\SubuserController@deleteSubuser', ]); @@ -169,12 +171,6 @@ class ServerRoutes // Assorted AJAX Routes $router->group(['prefix' => 'ajax'], function ($server) use ($router) { - // Returns Server Status - $router->get('status', [ - 'as' => 'server.ajax.status', - 'uses' => 'Server\AjaxController@getStatus', - ]); - // Sets the Default Connection for the Server $router->post('set-primary', [ 'uses' => 'Server\AjaxController@postSetPrimary', diff --git a/app/Models/APIKey.php b/app/Models/APIKey.php index 4b94b6782..68e481712 100644 --- a/app/Models/APIKey.php +++ b/app/Models/APIKey.php @@ -48,4 +48,14 @@ class APIKey extends Model * @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/Allocation.php b/app/Models/Allocation.php index 10308ebdd..cdcf79782 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -48,8 +48,40 @@ class Allocation extends Model * @var array */ protected $casts = [ - 'node' => 'integer', + 'node_id' => 'integer', 'port' => 'integer', - 'assigned_to' => '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; + } + + /** + * 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); + } } diff --git a/app/Models/Database.php b/app/Models/Database.php index 99e768c29..b5e9d39ad 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -55,7 +55,27 @@ class Database extends Model * @var array */ protected $casts = [ - 'server' => 'integer', + 'server_id' => 'integer', 'db_server' => 'integer', ]; + + /** + * Gets the host database server associated with a database. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function host() + { + return $this->belongsTo(DatabaseServer::class, 'db_server'); + } + + /** + * Gets the server associated with a database. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/app/Models/DatabaseServer.php b/app/Models/DatabaseServer.php index 74fd88694..1ebb7546f 100644 --- a/app/Models/DatabaseServer.php +++ b/app/Models/DatabaseServer.php @@ -59,4 +59,24 @@ class DatabaseServer extends Model 'server_id' => 'integer', 'db_server' => 'integer', ]; + + /** + * Gets the node associated with a database host. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class, 'linked_node'); + } + + /** + * Gets the databases assocaited with this host. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function databases() + { + return $this->hasMany(Database::class, 'db_server'); + } } diff --git a/app/Models/Location.php b/app/Models/Location.php index 5c8feb9fa..f9ceec767 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -41,4 +41,24 @@ class Location extends Model * @var array */ protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Gets the nodes in a specificed location. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function nodes() + { + return $this->hasMany(Node::class); + } + + /** + * Gets the servers within a given location. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function servers() + { + return $this->hasManyThrough(Server::class, Node::class); + } } diff --git a/app/Models/Node.php b/app/Models/Node.php index 5b82a2429..520df2a5e 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -26,9 +26,12 @@ namespace Pterodactyl\Models; use GuzzleHttp\Client; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; class Node extends Model { + use Notifiable; + /** * The table associated with the model. * @@ -50,7 +53,7 @@ class Node extends Model */ protected $casts = [ 'public' => 'integer', - 'location' => 'integer', + 'location_id' => 'integer', 'memory' => 'integer', 'disk' => 'integer', 'daemonListen' => 'integer', @@ -58,64 +61,33 @@ class Node extends Model ]; /** - * Fields that are not mass assignable. + * Fields that are mass assignable. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $fillable = [ + 'public', 'name', 'location_id', + 'fqdn', 'scheme', 'memory', + 'memory_overallocate', 'disk', + 'disk_overallocate', 'upload_size', + 'daemonSecret', 'daemonBase', + 'daemonSFTP', 'daemonListen', + ]; /** - * @var array - */ - protected static $guzzle = []; - - /** - * @var array - */ - protected static $nodes = []; - - /** - * Returns an instance of the database object for the requested node ID. + * Return an instance of the Guzzle client for this specific node. * - * @param int $id - * @return \Illuminate\Database\Eloquent\Model - */ - public static function getByID($id) - { - - // The Node is already cached. - if (array_key_exists($id, self::$nodes)) { - return self::$nodes[$id]; - } - - self::$nodes[$id] = self::where('id', $id)->first(); - - return self::$nodes[$id]; - } - - /** - * Returns a Guzzle Client for the node in question. - * - * @param int $node + * @param array $headers * @return \GuzzleHttp\Client */ - public static function guzzleRequest($node) + public function guzzleClient($headers = []) { - - // The Guzzle Client is cached already. - if (array_key_exists($node, self::$guzzle)) { - return self::$guzzle[$node]; - } - - $nodeData = self::getByID($node); - - self::$guzzle[$node] = new Client([ - 'base_uri' => sprintf('%s://%s:%s/', $nodeData->scheme, $nodeData->fqdn, $nodeData->daemonListen), - 'timeout' => 5.0, - 'connect_timeout' => 3.0, + return new Client([ + 'base_uri' => sprintf('%s://%s:%s/', $this->scheme, $this->fqdn, $this->daemonListen), + 'timeout' => env('GUZZLE_TIMEOUT', 5.0), + 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 3.0), + 'headers' => $headers, ]); - - return self::$guzzle[$node]; } /** @@ -167,11 +139,36 @@ class Node extends Model 'keys' => [$this->daemonSecret], ]; - $json_options = JSON_UNESCAPED_SLASHES; - if ($pretty) { - $json_options |= JSON_PRETTY_PRINT; - } + return json_encode($config, ($pretty) ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); + } - return json_encode($config, $json_options); + /** + * Gets the location associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function location() + { + return $this->belongsTo(Location::class); + } + + /** + * Gets the servers associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class); + } + + /** + * Gets the allocations associated with a node. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function allocations() + { + return $this->hasMany(Allocation::class); } } diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 008473f3c..7dfd55f3f 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -28,6 +28,13 @@ use Illuminate\Database\Eloquent\Model; class Permission extends Model { + /** + * Should timestamps be used on this model. + * + * @var bool + */ + public $timestamps = false; + /** * The table associated with the model. * @@ -42,22 +49,35 @@ class Permission extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'user_id' => 'integer', - 'server_id' => 'integer', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'subuser_id' => 'integer', + ]; + /** + * Find permission by permission node. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $permission + * @return \Illuminate\Database\Query\Builder + */ public function scopePermission($query, $permission) { return $query->where('permission', $permission); } - public function scopeServer($query, $server) + /** + * Filter permission by server. + * + * @param \Illuminate\Database\Query\Builder $query + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Database\Query\Builder + */ + public function scopeServer($query, Server $server) { return $query->where('server_id', $server->id); } diff --git a/app/Models/Server.php b/app/Models/Server.php index b90d0c510..7c69c763f 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -25,12 +25,15 @@ namespace Pterodactyl\Models; use Auth; +use Cache; +use Javascript; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\SoftDeletes; class Server extends Model { - use SoftDeletes; + use Notifiable, SoftDeletes; /** * The table associated with the model. @@ -66,96 +69,22 @@ class Server extends Model * @var array */ protected $casts = [ - 'node' => 'integer', + 'node_id' => 'integer', 'suspended' => 'integer', - 'owner' => 'integer', + 'owner_id' => 'integer', 'memory' => 'integer', 'swap' => 'integer', 'disk' => 'integer', 'io' => 'integer', 'cpu' => 'integer', 'oom_disabled' => 'integer', - 'port' => 'integer', - 'service' => 'integer', - 'option' => 'integer', + 'allocation_id' => 'integer', + 'service_id' => 'integer', + 'option_id' => 'integer', + 'pack_id' => 'integer', 'installed' => 'integer', ]; - /** - * @var array - */ - protected static $serverUUIDInstance = []; - - /** - * @var mixed - */ - protected static $user; - - /** - * Constructor. - */ - public function __construct() - { - parent::__construct(); - self::$user = Auth::user(); - } - - /** - * Determine if we need to change the server's daemonSecret value to - * match that of the user if they are a subuser. - * - * @param Illuminate\Database\Eloquent\Model\Server $server - * @return string - */ - public static function getUserDaemonSecret(Server $server) - { - if (self::$user->id === $server->owner || self::$user->root_admin === 1) { - return $server->daemonSecret; - } - - $subuser = Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first(); - - if (is_null($subuser)) { - return null; - } - - return $subuser->daemonSecret; - } - - /** - * Returns array of all servers owned by the logged in user. - * Returns all users servers if user is a root admin. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - public static function getUserServers($paginate = null) - { - $query = self::select( - 'servers.*', - 'nodes.name as nodeName', - 'locations.short as a_locationShort', - 'allocations.ip', - 'allocations.ip_alias', - 'allocations.port', - 'services.name as a_serviceName', - 'service_options.name as a_serviceOptionName' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->join('services', 'servers.service', '=', 'services.id') - ->join('service_options', 'servers.option', '=', 'service_options.id') - ->join('allocations', 'servers.allocation', '=', 'allocations.id'); - - if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Subuser::accessServers()); - } - - if (is_numeric($paginate)) { - return $query->paginate($paginate); - } - - return $query->get(); - } - /** * Returns a single server specified by UUID. * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. @@ -164,30 +93,26 @@ class Server extends Model * @param string $uuid The Short-UUID of the server to return an object about. * @return \Illuminate\Database\Eloquent\Collection */ - public static function getByUUID($uuid) + public static function byUuid($uuid) { - if (array_key_exists($uuid, self::$serverUUIDInstance)) { - return self::$serverUUIDInstance[$uuid]; - } + // Results are cached because we call this functions a few times on page load. + $result = Cache::remember('Server.byUuid.' . $uuid . Auth::user()->uuid, 60, function () use ($uuid) { + $query = self::with('service', 'node')->where(function ($q) use ($uuid) { + $q->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + }); - $query = self::select('servers.*', 'services.file as a_serviceFile') - ->join('services', 'services.id', '=', 'servers.service') - ->where('uuidShort', $uuid) - ->orWhere('uuid', $uuid); + if (! Auth::user()->isRootAdmin()) { + $query->whereIn('id', Auth::user()->serverAccessArray()); + } - if (self::$user->root_admin !== 1) { - $query->whereIn('servers.id', Subuser::accessServers()); - } - - $result = $query->first(); + return $query->first(); + }); if (! is_null($result)) { - $result->daemonSecret = self::getUserDaemonSecret($result); + $result->daemonSecret = Auth::user()->daemonToken($result); } - self::$serverUUIDInstance[$uuid] = $result; - - return self::$serverUUIDInstance[$uuid]; + return $result; } /** @@ -196,15 +121,164 @@ class Server extends Model * @param string $uuid * @return array */ - public static function getGuzzleHeaders($uuid) + public function guzzleHeaders() { - if (array_key_exists($uuid, self::$serverUUIDInstance)) { - return [ - 'X-Access-Server' => self::$serverUUIDInstance[$uuid]->uuid, - 'X-Access-Token' => self::$serverUUIDInstance[$uuid]->daemonSecret, - ]; + return [ + 'X-Access-Server' => $this->uuid, + 'X-Access-Token' => Auth::user()->daemonToken($this), + ]; + } + + /** + * Return an instance of the Guzzle client for this specific server using defined access token. + * + * @return \GuzzleHttp\Client + */ + public function guzzleClient() + { + return $this->node->guzzleClient($this->guzzleHeaders()); + } + + /** + * Returns javascript object to be embedded on server view pages with relevant information. + * + * @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); } - return []; + if (is_array($overwrite)) { + $response = $overwrite; + } + + return Javascript::put($response); + } + + /** + * Gets the user who owns the server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class, 'owner_id'); + } + + /** + * Gets the subusers associated with a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function subusers() + { + return $this->hasMany(Subuser::class); + } + + /** + * Gets the default allocation for a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function allocation() + { + return $this->hasOne(Allocation::class, 'id', 'allocation_id'); + } + + /** + * Gets all allocations associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function allocations() + { + return $this->hasMany(Allocation::class, 'server_id'); + } + + /** + * Gets information for the pack associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function pack() + { + return $this->hasOne(ServicePack::class, 'id', 'pack_id'); + } + + /** + * Gets information for the service associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function service() + { + return $this->belongsTo(Service::class); + } + + /** + * Gets information for the service option associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function option() + { + return $this->belongsTo(ServiceOption::class); + } + + /** + * Gets information for the service variables associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function variables() + { + return $this->hasMany(ServerVariable::class); + } + + /** + * Gets information for the node associated with this server. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class); + } + + /** + * 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() + { + return $this->hasMany(Task::class, 'server', 'id'); + } + + /** + * Gets all databases associated with a server. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function databases() + { + return $this->hasMany(Database::class); } } diff --git a/app/Models/ServerVariables.php b/app/Models/ServerVariable.php similarity index 84% rename from app/Models/ServerVariables.php rename to app/Models/ServerVariable.php index c6c6970bc..e92c5caf2 100644 --- a/app/Models/ServerVariables.php +++ b/app/Models/ServerVariable.php @@ -26,7 +26,7 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; -class ServerVariables extends Model +class ServerVariable extends Model { /** * The table associated with the model. @@ -51,4 +51,14 @@ class ServerVariables extends Model 'server_id' => 'integer', 'variable_id' => 'integer', ]; + + /** + * Returns information about a given variables parent. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function variable() + { + return $this->belongsTo(ServiceVariable::class, 'variable_id'); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 8c0f27790..83cdb70b0 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -36,9 +36,44 @@ class Service extends Model protected $table = 'services'; /** - * Fields that are not mass assignable. + * Fields that are mass assignable. * * @var array */ - protected $guarded = ['id', 'created_at', 'updated_at']; + protected $fillable = [ + 'name', 'description', 'file', 'executable', 'startup', + ]; + + /** + * 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\ServicePack', '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 new file mode 100644 index 000000000..adf646d88 --- /dev/null +++ b/app/Models/ServiceOption.php @@ -0,0 +1,115 @@ +. + * + * 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', + ]; + + /** + * Returns the display executable for the option and will use the parent + * service one if the option does not have one defined. + * + * @return string + */ + public function getDisplayExecutableAttribute($value) + { + return (is_null($this->executable)) ? $this->service->executable : $this->executable; + } + + /** + * 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; + } + + /** + * 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(ServicePack::class, 'option_id'); + } +} diff --git a/app/Models/ServicePack.php b/app/Models/ServicePack.php index cefcc0a32..e82f72411 100644 --- a/app/Models/ServicePack.php +++ b/app/Models/ServicePack.php @@ -56,4 +56,14 @@ class ServicePack extends Model 'selectable' => 'boolean', 'visible' => 'boolean', ]; + + /** + * Gets option associated with a service pack. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function option() + { + return $this->belongsTo(ServiceOption::class); + } } diff --git a/app/Models/ServiceVariables.php b/app/Models/ServiceVariable.php similarity index 92% rename from app/Models/ServiceVariables.php rename to app/Models/ServiceVariable.php index 5082d714e..aedfc6351 100644 --- a/app/Models/ServiceVariables.php +++ b/app/Models/ServiceVariable.php @@ -26,7 +26,7 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Model; -class ServiceVariables extends Model +class ServiceVariable extends Model { /** * The table associated with the model. @@ -53,4 +53,9 @@ class ServiceVariables extends Model 'user_editable' => 'integer', 'required' => 'integer', ]; + + public function serverVariable() + { + return $this->hasMany(ServerVariable::class, 'variable_id'); + } } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 8d4a5b133..a2eff9c9a 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -24,11 +24,13 @@ namespace Pterodactyl\Models; -use Auth; use Illuminate\Database\Eloquent\Model; +use Illuminate\Notifications\Notifiable; class Subuser extends Model { + use Notifiable; + /** * The table associated with the model. * @@ -50,38 +52,43 @@ class Subuser extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'user_id' => 'integer', - 'server_id' => 'integer', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'user_id' => 'integer', + 'server_id' => 'integer', + ]; /** - * @var mixed + * Gets the server associated with a subuser. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - protected static $user; - - /** - * Constructor. - */ - public function __construct() + public function server() { - self::$user = Auth::user(); + return $this->belongsTo(Server::class); } /** - * Returns an array of each server ID that the user has access to. + * Gets the user associated with a subuser. * - * @return array + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public static function accessServers() + public function user() { - $union = self::select('server_id')->where('user_id', self::$user->id); + return $this->belongsTo(User::class); + } - return Server::select('id')->where('owner', self::$user->id)->union($union)->pluck('id'); + /** + * Gets the permissions associated with a subuser. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function permissions() + { + return $this->hasMany(Permission::class); } } diff --git a/app/Models/User.php b/app/Models/User.php index 8d1d7fb71..131192264 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -87,16 +87,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac */ protected $hidden = ['password', 'remember_token', 'totp_secret']; - /** - * Determines if a user has permissions. - * - * @return bool - */ - public function permissions() - { - return $this->hasMany(Permission::class); - } - /** * Enables or disables TOTP on an account if the token is valid. * @@ -105,7 +95,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac */ public function toggleTotp($token) { - if (! Google2FA::verifyKey($this->totp_secret, $token)) { + if (! Google2FA::verifyKey($this->totp_secret, $token, 1)) { return false; } @@ -156,4 +146,73 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac { return $this->root_admin === 1; } + + /** + * Returns the user's daemon secret for a given server. + * @param Server $server \Pterodactyl\Models\Server + * @return null|string + */ + public function daemonToken(Server $server) + { + if ($this->id === $server->owner_id || $this->isRootAdmin()) { + return $server->daemonSecret; + } + + $subuser = Subuser::where('server_id', $server->id)->where('user_id', $this->id)->first(); + + if (is_null($subuser)) { + return null; + } + + return $subuser->daemonSecret; + } + + /** + * 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() + { + $union = Subuser::select('server_id')->where('user_id', $this->id); + + return Server::select('id')->where('owner_id', $this->id)->union($union)->pluck('id')->all(); + } + + /** + * Returns an array of all servers a user is able to access. + * Note: does not account for user admin status. + * + * @return Collection + */ + public function serverAccessCollection($paginate = null, $load = ['service', 'node', 'allocation']) + { + $query = Server::with($load); + if (! $this->isRootAdmin()) { + $query->whereIn('id', $this->serverAccessArray()); + } + + return (is_numeric($paginate)) ? $query->paginate($paginate) : $query->get(); + } + + /** + * Returns all permissions that a user has. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function permissions() + { + return $this->hasManyThrough(Permission::class, Subuser::class); + } + + /** + * Returns all servers that a user owns. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class, 'owner_id'); + } } diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index 55cfe416b..b7f86361c 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -38,16 +38,16 @@ class AccountCreated extends Notification implements ShouldQueue * * @var string */ - public $token; + public $user; /** * Create a new notification instance. * * @return void */ - public function __construct($token) + public function __construct(array $user) { - $this->token = $token; + $this->user = (object) $user; } /** @@ -69,9 +69,16 @@ class AccountCreated extends Notification implements ShouldQueue */ public function toMail($notifiable) { - return (new MailMessage) - ->line('You are recieving this email because an account has been created for you on Pterodactyl Panel.') - ->line('Email: ' . $notifiable->email) - ->action('Setup Your Account', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email)); + $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('Username: ' . $this->user->username) + ->line('Email: ' . $notifiable->email); + + if (! is_null($this->user->token)) { + return $message->action('Setup Your Account', url('/auth/password/reset/' . $this->user->token . '?email=' . $notifiable->email)); + } + + return $message; } } diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php new file mode 100644 index 000000000..243584f13 --- /dev/null +++ b/app/Notifications/AddedToServer.php @@ -0,0 +1,74 @@ +. + * + * 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 AddedToServer extends Notification implements ShouldQueue +{ + use Queueable; + + 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) + ->greeting('Hello ' . $this->server->user . '!') + ->line('You have been added as a subuser for the following server, allowing you certain control over the server.') + ->line('Server Name: ' . $this->server->name) + ->action('Visit Server', route('server.index', $this->server->uuidShort)); + } +} diff --git a/app/Notifications/RemovedFromServer.php b/app/Notifications/RemovedFromServer.php new file mode 100644 index 000000000..d2a7599dc --- /dev/null +++ b/app/Notifications/RemovedFromServer.php @@ -0,0 +1,75 @@ +. + * + * 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 RemovedFromServer extends Notification implements ShouldQueue +{ + use Queueable; + + 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) + ->error() + ->greeting('Hello ' . $this->server->user . '.') + ->line('You have been removed as a subuser for the following server.') + ->line('Server Name: ' . $this->server->name) + ->action('Visit Panel', route('index')); + } +} diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index 31939945e..0037fb7f3 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -72,20 +72,7 @@ class SendPasswordReset extends Notification implements ShouldQueue return (new MailMessage) ->subject('Reset Password') ->line('You are receiving this email because we received a password reset request for your account.') - ->action('Reset Password', url('auth/password/reset', $this->token)) + ->action('Reset Password', url('/auth/password/reset/' . $this->token . '?email=' . $notifiable->email)) ->line('If you did not request a password reset, no further action is required.'); } - - /** - * Get the array representation of the notification. - * - * @param mixed $notifiable - * @return array - */ - public function toArray($notifiable) - { - return [ - // - ]; - } } diff --git a/app/Notifications/ServerCreated.php b/app/Notifications/ServerCreated.php index a581a8fb2..a867e26c7 100644 --- a/app/Notifications/ServerCreated.php +++ b/app/Notifications/ServerCreated.php @@ -1,4 +1,26 @@ . + * + * 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; @@ -44,12 +66,12 @@ class ServerCreated extends Notification implements ShouldQueue 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!'); + ->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 new file mode 100644 index 000000000..5d919a559 --- /dev/null +++ b/app/Observers/ServerObserver.php @@ -0,0 +1,150 @@ +. + * + * 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 Auth; +use Cache; +use Carbon; +use Pterodactyl\Events; +use Pterodactyl\Models\Server; +use Pterodactyl\Jobs\DeleteServer; +use Pterodactyl\Jobs\SuspendServer; +use Pterodactyl\Notifications\ServerCreated; +use Illuminate\Foundation\Bus\DispatchesJobs; + +class ServerObserver +{ + use DispatchesJobs; + + /** + * Listen to the Server creating event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function creating(Server $server) + { + event(new Events\Server\Creating($server)); + } + + /** + * Listen to the Server created event. + * + * @param Server $server [description] + * @return [type] [description] + */ + 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 Server $server [description] + * @return [type] [description] + */ + public function deleting(Server $server) + { + event(new Events\Server\Deleting($server)); + + $this->dispatch((new SuspendServer($server->id))->onQueue(env('QUEUE_HIGH', 'high'))); + } + + /** + * Listen to the Server deleted event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function deleted(Server $server) + { + event(new Events\Server\Deleted($server)); + + $this->dispatch( + (new DeleteServer($server->id)) + ->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10))) + ->onQueue(env('QUEUE_STANDARD', 'standard')) + ); + } + + /** + * Listen to the Server saving event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function saving(Server $server) + { + event(new Events\Server\Saving($server)); + } + + /** + * Listen to the Server saved event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function saved(Server $server) + { + event(new Events\Server\Saved($server)); + } + + /** + * Listen to the Server saving event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function updating(Server $server) + { + event(new Events\Server\Updating($server)); + } + + /** + * Listen to the Server saved event. + * + * @param Server $server [description] + * @return [type] [description] + */ + public function updated(Server $server) + { + // Clear Caches + Cache::forget('Server.byUuid.' . $server->uuid . Auth::user()->uuid); + Cache::forget('Server.byUuid.' . $server->uuidShort . Auth::user()->uuid); + + event(new Events\Server\Updated($server)); + } +} diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php new file mode 100644 index 000000000..b3d64d7d1 --- /dev/null +++ b/app/Observers/SubuserObserver.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\Observers; + +use Pterodactyl\Events; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Notifications\AddedToServer; +use Pterodactyl\Notifications\RemovedFromServer; + +class SubuserObserver +{ + /** + * Listen to the Subuser creating event. + * + * @param Subuser $subuser The eloquent Subuser model. + * @return void + */ + public function creating(Subuser $subuser) + { + event(new Events\Subuser\Creating($subuser)); + } + + /** + * Listen to the Subuser created event. + * + * @param Subuser $subuser The eloquent Subuser model. + * @return void + */ + public function created(Subuser $subuser) + { + event(new Events\Subuser\Created($subuser)); + + $subuser->user->notify((new AddedToServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + 'uuidShort' => $subuser->server->uuidShort, + ]))); + } + + /** + * Listen to the Subuser deleting event. + * + * @param Subuser $subuser The eloquent Subuser model. + * @return void + */ + public function deleting(Subuser $subuser) + { + event(new Events\Subuser\Deleting($subuser)); + } + + /** + * Listen to the Subuser deleted event. + * + * @param Subuser $subuser The eloquent Subuser model. + * @return void + */ + public function deleted(Subuser $subuser) + { + event(new Events\Subuser\Deleted($subuser)); + + $subuser->user->notify((new RemovedFromServer([ + 'user' => $subuser->user->name_first, + 'name' => $subuser->server->name, + ]))); + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php new file mode 100644 index 000000000..258174ffb --- /dev/null +++ b/app/Observers/UserObserver.php @@ -0,0 +1,84 @@ +. + * + * 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 Pterodactyl\Events; +use Pterodactyl\Models\User; +use Pterodactyl\Notifications\AccountCreated; + +class UserObserver +{ + /** + * Listen to the User creating event. + * + * @param User $user The eloquent User model. + * @return void + */ + public function creating(User $user) + { + event(new Events\User\Creating($user)); + } + + /** + * Listen to the User created event. + * + * @param User $user The eloquent User model. + * @return void + */ + public function created(User $user) + { + event(new Events\User\Created($user)); + + $token = DB::table('password_resets')->where('email', $user->email)->orderBy('created_at', 'desc')->first(); + $user->notify((new AccountCreated([ + 'name' => $user->name_first, + 'username' => $user->username, + 'token' => (! is_null($token)) ? $token->token : null, + ]))); + } + + /** + * Listen to the User deleting event. + * + * @param User $user The eloquent User model. + * @return void + */ + public function deleting(User $user) + { + event(new Events\User\Deleting($user)); + } + + /** + * Listen to the User deleted event. + * + * @param User $user The eloquent User model. + * @return void + */ + public function deleted(User $user) + { + event(new Events\User\Deleted($user)); + } +} diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index f297fe829..767dec13c 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -24,6 +24,8 @@ namespace Pterodactyl\Policies; +use Cache; +use Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -39,6 +41,29 @@ class ServerPolicy // } + /** + * Checks if the user has the given permission on/for the server. + * + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\Server $server + * @param $permission + * @return bool + */ + private function checkPermission(User $user, Server $server, $permission) + { + if ($this->isOwner($user, $server)) { + return true; + } + + $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(10), function () use ($user, $server) { + return $user->permissions()->server($server)->get()->transform(function ($item) { + return $item->permission; + })->values(); + }); + + return $permissions->search($permission, true) !== false; + } + /** * Determine if current user is the owner of a server. * @@ -48,7 +73,7 @@ class ServerPolicy */ protected function isOwner(User $user, Server $server) { - return $server->owner === $user->id; + return $server->owner_id === $user->id; } /** @@ -521,21 +546,4 @@ class ServerPolicy { return $this->checkPermission($user, $server, 'set-allocation'); } - - /** - * Checks if the user has the given permission on/for the server. - * - * @param \Pterodactyl\Models\User $user - * @param \Pterodactyl\Models\Server $server - * @param $permission - * @return bool - */ - private function checkPermission(User $user, Server $server, $permission) - { - if ($this->isOwner($user, $server)) { - return true; - } - - return $user->permissions()->server($server)->permission($permission)->exists(); - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d0d1dd9e4..a1181fb10 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -1,7 +1,31 @@ . + * + * 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 Pterodactyl\Models; +use Pterodactyl\Observers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -13,7 +37,9 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - // + Models\User::observe(Observers\UserObserver::class); + Models\Server::observe(Observers\ServerObserver::class); + Models\Subuser::observe(Observers\SubuserObserver::class); } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7ec0d48ae..0e31100c3 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -12,11 +12,7 @@ class EventServiceProvider extends ServiceProvider * * @var array */ - protected $listen = [ - 'Pterodactyl\Events\ServerDeleted' => [ - 'Pterodactyl\Listeners\DeleteServerListener', - ], - ]; + protected $listen = []; /** * Register any other events for your application. diff --git a/app/Providers/PhraseAppTranslationProvider.php b/app/Providers/PhraseAppTranslationProvider.php new file mode 100644 index 000000000..e6ba42116 --- /dev/null +++ b/app/Providers/PhraseAppTranslationProvider.php @@ -0,0 +1,61 @@ +. + * + * 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 Pterodactyl\Extensions\PhraseAppTranslator; +use Illuminate\Translation\TranslationServiceProvider; +use Illuminate\Translation\Translator as IlluminateTranslator; + +class PhraseAppTranslationProvider extends TranslationServiceProvider +{ + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerLoader(); + + $this->app->singleton('translator', function ($app) { + $loader = $app['translation.loader']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + if ($app['config']['app.phrase_in_context']) { + $trans = new PhraseAppTranslator($loader, $locale); + } else { + $trans = new IlluminateTranslator($loader, $locale); + } + + $trans->setFallback($app['config']['app.fallback_locale']); + + return $trans; + }); + } +} diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php index 7ce94f34b..5f148eda6 100644 --- a/app/Repositories/APIRepository.php +++ b/app/Repositories/APIRepository.php @@ -62,8 +62,8 @@ class APIRepository // Node Management Routes 'nodes.list', + 'nodes.view', 'nodes.create', - 'nodes.list', 'nodes.allocations', 'nodes.delete', @@ -102,7 +102,7 @@ class APIRepository { $this->user = is_null($user) ? Auth::user() : $user; if (is_null($this->user)) { - throw new \Exception('Cannot access API Repository without passing a user to __construct().'); + throw new \Exception('Cannot access API Repository without passing a user to constructor.'); } } @@ -149,7 +149,7 @@ class APIRepository $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); $key = new Models\APIKey; $key->fill([ - 'user' => $this->user->id, + 'user_id' => $this->user->id, 'public' => str_random(16), 'secret' => Crypt::encrypt($secretKey), 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), @@ -178,7 +178,7 @@ class APIRepository } } - if ($this->user->root_admin === 1 && isset($data['adminPermissions'])) { + if ($this->user->isRootAdmin() && isset($data['adminPermissions'])) { foreach ($data['adminPermissions'] as $permNode) { if (! strpos($permNode, ':')) { continue; @@ -224,8 +224,11 @@ class APIRepository DB::beginTransaction(); try { - $model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail(); - Models\APIPermission::where('key_id', $model->id)->delete(); + $model = Models\APIKey::with('permissions')->where('public', $key)->where('user_id', $this->user->id)->firstOrFail(); + foreach ($model->permissions as &$permission) { + $permission->delete(); + } + $model->delete(); DB::commit(); diff --git a/app/Repositories/Daemon/CommandRepository.php b/app/Repositories/Daemon/CommandRepository.php index 5631518dc..76ac93178 100644 --- a/app/Repositories/Daemon/CommandRepository.php +++ b/app/Repositories/Daemon/CommandRepository.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Repositories\Daemon; -use GuzzleHttp\Client; use Pterodactyl\Models; use GuzzleHttp\Exception\RequestException; use Pterodactyl\Exceptions\DisplayException; @@ -32,14 +31,10 @@ use Pterodactyl\Exceptions\DisplayException; class CommandRepository { protected $server; - protected $node; - protected $client; public function __construct($server) { $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); - $this->node = Models\Node::getByID($this->server->node); - $this->client = Models\Node::guzzleRequest($this->server->node); } /** @@ -56,15 +51,10 @@ class CommandRepository // Additionally not all calls to this will be from a logged in user. // (e.g. task queue or API) try { - $response = $this->client->request('POST', '/server/command', [ - 'headers' => [ - 'X-Access-Token' => $this->server->daemonSecret, - 'X-Access-Server' => $this->server->uuid, - ], - 'json' => [ - 'command' => $command, - ], - ]); + $response = $this->server->node->guzzleClient([ + 'X-Access-Token' => $this->server->daemonSecret, + 'X-Access-Server' => $this->server->uuid, + ])->request('POST', '/server/command', ['json' => ['command' => $command]]); if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { throw new DisplayException('Command sending responded with a non-200 error code.'); diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php index d1319cf91..5fc2245fb 100644 --- a/app/Repositories/Daemon/FileRepository.php +++ b/app/Repositories/Daemon/FileRepository.php @@ -26,7 +26,6 @@ namespace Pterodactyl\Repositories\Daemon; use Exception; use GuzzleHttp\Client; -use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\HelperRepository; @@ -40,28 +39,6 @@ class FileRepository */ protected $server; - /** - * The Eloquent Model for the node corresponding with the requested server. - * - * @var \Illuminate\Database\Eloquent\Model - */ - protected $node; - - /** - * The Guzzle Client associated with the requested server and node. - * - * @var \GuzzleHttp\Client - */ - protected $client; - - /** - * The Guzzle Client headers associated with the requested server and node. - * (non-administrative headers). - * - * @var array - */ - protected $headers; - /** * Constructor. * @@ -69,10 +46,7 @@ class FileRepository */ public function __construct($uuid) { - $this->server = Server::getByUUID($uuid); - $this->node = Node::getByID($this->server->node); - $this->client = Node::guzzleRequest($this->server->node); - $this->headers = Server::getGuzzleHeaders($uuid); + $this->server = Server::byUuid($uuid); } /** @@ -88,12 +62,9 @@ class FileRepository } $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; - $res = $this->client->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename), [ - 'headers' => $this->headers, - ]); + $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)) { @@ -108,9 +79,7 @@ class FileRepository throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.'); } - $res = $this->client->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename), [ - 'headers' => $this->headers, - ]); + $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)) { @@ -137,11 +106,9 @@ class FileRepository } $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; - $res = $this->client->request('POST', '/server/file/save', [ - 'headers' => $this->headers, + $res = $this->server->guzzleClient()->request('POST', '/server/file/save', [ 'json' => [ 'path' => rawurlencode($file->dirname . $file->basename), 'content' => $content, @@ -167,9 +134,7 @@ class FileRepository throw new Exception('A valid directory must be specified in order to list its contents.'); } - $res = $this->client->request('GET', '/server/directory/' . rawurlencode($directory), [ - 'headers' => $this->headers, - ]); + $res = $this->server->guzzleClient()->request('GET', '/server/directory/' . rawurlencode($directory)); $json = json_decode($res->getBody()); if ($res->getStatusCode() !== 200) { @@ -180,7 +145,7 @@ class FileRepository $files = []; $folders = []; foreach ($json as &$value) { - if ($value->directory === true) { + if ($value->directory) { // @TODO Handle Symlinks $folders[] = [ 'entry' => $value->name, @@ -189,7 +154,7 @@ class FileRepository 'date' => strtotime($value->modified), 'mime' => $value->mime, ]; - } elseif ($value->file === true) { + } elseif ($value->file) { $files[] = [ 'entry' => $value->name, 'directory' => trim($directory, '/'), diff --git a/app/Repositories/Daemon/PowerRepository.php b/app/Repositories/Daemon/PowerRepository.php index 705ea3e82..b31c0cda0 100644 --- a/app/Repositories/Daemon/PowerRepository.php +++ b/app/Repositories/Daemon/PowerRepository.php @@ -24,21 +24,16 @@ namespace Pterodactyl\Repositories\Daemon; -use GuzzleHttp\Client; use Pterodactyl\Models; use Pterodactyl\Exceptions\DisplayException; class PowerRepository { protected $server; - protected $node; - protected $client; public function __construct($server) { $this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server); - $this->node = Models\Node::getByID($this->server->node); - $this->client = Models\Node::guzzleRequest($this->server->node); } public function do($action) @@ -48,15 +43,10 @@ class PowerRepository // Additionally not all calls to this will be from a logged in user. // (e.g. task queue or API) try { - $response = $this->client->request('PUT', '/server/power', [ - 'headers' => [ - 'X-Access-Token' => $this->server->daemonSecret, - 'X-Access-Server' => $this->server->uuid, - ], - 'json' => [ - 'action' => $action, - ], - ]); + $response = $this->server->node->guzzleClient([ + 'X-Access-Token' => $this->server->daemonSecret, + 'X-Access-Server' => $this->server->uuid, + ])->request('PUT', '/server/power', ['json' => ['action' => $action]]); if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { throw new DisplayException('Power status responded with a non-200 error code.'); diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php index 1abf8e155..c605a32de 100644 --- a/app/Repositories/DatabaseRepository.php +++ b/app/Repositories/DatabaseRepository.php @@ -114,28 +114,27 @@ class DatabaseRepository /** * Updates the password for a given database. - * @param int $database The ID of the database to modify. + * @param int $id The ID of the database to modify. * @param string $password The new password to use for the database. * @return bool */ - public function modifyPassword($database, $password) + public function modifyPassword($id, $password) { - $db = Models\Database::findOrFail($database); - $dbr = Models\DatabaseServer::findOrFail($db->db_server); + $database = Models\Database::with('host')->findOrFail($id); DB::beginTransaction(); try { - $db->password = Crypt::encrypt($password); - $db->save(); + $database->password = Crypt::encrypt($password); + $database->save(); $capsule = new Capsule; $capsule->addConnection([ 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, + 'host' => $database->host->host, + 'port' => $database->host->port, 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), + 'username' => $database->host->username, + 'password' => Crypt::decrypt($database->host->password), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', @@ -147,8 +146,8 @@ class DatabaseRepository $capsule->setAsGlobal(); Capsule::statement(sprintf( 'SET PASSWORD FOR `%s`@`%s` = PASSWORD(\'%s\')', - $db->username, - $db->remote, + $database->username, + $database->remote, $password )); @@ -161,13 +160,12 @@ class DatabaseRepository /** * Drops a database from the associated MySQL Server. - * @param int $database The ID of the database to drop. + * @param int $id The ID of the database to drop. * @return bool */ - public function drop($database) + public function drop($id) { - $db = Models\Database::findOrFail($database); - $dbr = Models\DatabaseServer::findOrFail($db->db_server); + $database = Models\Database::with('host')->findOrFail($id); DB::beginTransaction(); @@ -175,11 +173,11 @@ class DatabaseRepository $capsule = new Capsule; $capsule->addConnection([ 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, + 'host' => $database->host->host, + 'port' => $database->host->port, 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), + 'username' => $database->host->username, + 'password' => Crypt::decrypt($database->host->password), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', @@ -190,10 +188,10 @@ class DatabaseRepository $capsule->setAsGlobal(); - Capsule::statement('DROP USER `' . $db->username . '`@`' . $db->remote . '`'); - Capsule::statement('DROP DATABASE `' . $db->database . '`'); + Capsule::statement('DROP USER `' . $database->username . '`@`' . $database->remote . '`'); + Capsule::statement('DROP DATABASE `' . $database->database . '`'); - $db->delete(); + $database->delete(); DB::commit(); @@ -206,19 +204,19 @@ class DatabaseRepository /** * Deletes a database server from the system if it is empty. + * * @param int $server The ID of the Database Server. * @return */ public function delete($server) { - $dbh = Models\DatabaseServer::findOrFail($server); - $databases = Models\Database::where('db_server', $dbh->id)->count(); + $host = Models\DatabaseServer::withCount('databases')->findOrFail($server); - if ($databases > 0) { + if ($host->databases_count > 0) { throw new DisplayException('You cannot delete a database server that has active databases attached to it.'); } - return $dbh->delete(); + return $host->delete(); } /** @@ -268,8 +266,7 @@ class DatabaseRepository // Allows us to check that we can connect to things. Capsule::select('SELECT 1 FROM dual'); - $dbh = new Models\DatabaseServer; - $dbh->fill([ + Models\DatabaseServer::create([ 'name' => $data['name'], 'host' => $data['host'], 'port' => $data['port'], @@ -278,7 +275,6 @@ class DatabaseRepository 'max_databases' => null, 'linked_node' => (! empty($data['linked_node']) && $data['linked_node'] > 0) ? $data['linked_node'] : null, ]); - $dbh->save(); DB::commit(); } catch (\Exception $ex) { diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/LocationRepository.php index 79196ff35..1b6f50613 100644 --- a/app/Repositories/LocationRepository.php +++ b/app/Repositories/LocationRepository.php @@ -37,14 +37,15 @@ class LocationRepository /** * Creates a new location on the system. + * * @param array $data - * @throws Pterodactyl\Exceptions\DisplayValidationException - * @return int + * @throws \Pterodactyl\Exceptions\DisplayValidationException + * @return \Pterodactyl\Models\Location */ public function create(array $data) { $validator = Validator::make($data, [ - 'short' => 'required|regex:/^[a-z0-9_.-]{1,10}$/i|unique:locations,short', + 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short', 'long' => 'required|string|min:1|max:255', ]); @@ -54,14 +55,12 @@ class LocationRepository throw new DisplayValidationException($validator->errors()); } - $location = new Models\Location; - $location->fill([ + $location = Models\Location::create([ 'long' => $data['long'], 'short' => $data['short'], ]); - $location->save(); - return $location->id; + return $location; } /** @@ -73,9 +72,11 @@ class LocationRepository */ public function edit($id, array $data) { + $location = Models\Location::findOrFail($id); + $validator = Validator::make($data, [ - 'short' => 'regex:/^[a-z0-9_.-]{1,10}$/i', - 'long' => 'string|min:1|max:255', + 'short' => 'required|regex:/^[\w.-]{1,20}$/i|unique:locations,short,' . $location->id, + 'long' => 'required|string|min:1|max:255', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -84,15 +85,7 @@ class LocationRepository throw new DisplayValidationException($validator->errors()); } - $location = Models\Location::findOrFail($id); - - if (isset($data['short'])) { - $location->short = $data['short']; - } - - if (isset($data['long'])) { - $location->long = $data['long']; - } + $location->fill($data); return $location->save(); } diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php index d55446196..56dbd37c2 100644 --- a/app/Repositories/NodeRepository.php +++ b/app/Repositories/NodeRepository.php @@ -44,7 +44,7 @@ class NodeRepository // Validate Fields $validator = Validator::make($data, [ 'name' => 'required|regex:/^([\w .-]{1,100})$/', - 'location' => 'required|numeric|min:1|exists:locations,id', + '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)?)$/', @@ -65,7 +65,7 @@ class NodeRepository // 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 secure comunication on this node.'); + 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. @@ -81,12 +81,7 @@ class NodeRepository $uuid = new UuidService; $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - // Store the Data - $node = new Models\Node; - $node->fill($data); - $node->save(); - - return $node->id; + return Models\Node::create($data); } public function update($id, array $data) @@ -96,7 +91,7 @@ class NodeRepository // Validate Fields $validator = $validator = Validator::make($data, [ 'name' => 'regex:/^([\w .-]{1,100})$/', - 'location' => 'numeric|min:1|exists:locations,id', + 'location_id' => 'numeric|min:1|exists:locations,id', 'public' => 'numeric|between:0,1', 'fqdn' => 'string|unique:nodes,fqdn,' . $id, 'scheme' => 'regex:/^(http(s)?)$/', @@ -105,10 +100,10 @@ class NodeRepository 'disk' => 'numeric|min:1', 'disk_overallocate' => 'numeric|min:-1', 'upload_size' => 'numeric|min:0', - 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', + 'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/', 'daemonSFTP' => 'numeric|between:1,65535', 'daemonListen' => 'numeric|between:1,65535', - 'reset_secret' => 'sometimes|accepted', + 'reset_secret' => 'sometimes|nullable|accepted', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -143,7 +138,7 @@ class NodeRepository } // Set the Secret - if (isset($data['reset_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']); @@ -152,18 +147,12 @@ class NodeRepository $oldDaemonKey = $node->daemonSecret; $node->update($data); try { - $client = Models\Node::guzzleRequest($node->id); - $client->request('PATCH', '/config', [ - 'headers' => [ - 'X-Access-Token' => $oldDaemonKey, - ], + $node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [ 'json' => [ 'web' => [ 'listen' => $node->daemonListen, 'ssl' => [ 'enabled' => ($node->scheme === 'https'), - 'certificate' => '/etc/letsencrypt/live/' . $node->fqdn . '/fullchain.pem', - 'key' => '/etc/letsencrypt/live/' . $node->fqdn . '/privkey.pem', ], ], 'sftp' => [ @@ -221,34 +210,34 @@ class NodeRepository foreach ($portBlock as $assignPort) { $alloc = Models\Allocation::firstOrNew([ - 'node' => $node->id, + 'node_id' => $node->id, 'ip' => $ip, 'port' => $assignPort, ]); if (! $alloc->exists) { $alloc->fill([ - 'node' => $node->id, + 'node_id' => $node->id, 'ip' => $ip, 'port' => $assignPort, 'ip_alias' => $setAlias, - 'assigned_to' => null, + 'server_id' => null, ]); $alloc->save(); } } } else { $alloc = Models\Allocation::firstOrNew([ - 'node' => $node->id, + 'node_id' => $node->id, 'ip' => $ip, 'port' => $port, ]); if (! $alloc->exists) { $alloc->fill([ - 'node' => $node->id, + 'node_id' => $node->id, 'ip' => $ip, 'port' => $port, 'ip_alias' => $setAlias, - 'assigned_to' => null, + 'server_id' => null, ]); $alloc->save(); } @@ -266,8 +255,8 @@ class NodeRepository public function delete($id) { - $node = Models\Node::findOrFail($id); - if (Models\Server::where('node', $id)->count() > 0) { + $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.'); } @@ -280,7 +269,7 @@ class NodeRepository ]); // Delete Allocations - Models\Allocation::where('node', $node->id)->delete(); + Models\Allocation::where('node_id', $node->id)->delete(); // Delete configure tokens Models\NodeConfigurationToken::where('node', $node->id)->delete(); diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 88618a476..4b5ed1f15 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -29,11 +29,10 @@ use Log; use Crypt; use Validator; use Pterodactyl\Models; -use Pterodactyl\Events\ServerDeleted; use Pterodactyl\Services\UuidService; +use GuzzleHttp\Exception\TransferException; use Pterodactyl\Services\DeploymentService; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Notifications\ServerCreated; use Pterodactyl\Exceptions\DisplayValidationException; class ServerRepository @@ -91,16 +90,17 @@ class ServerRepository 'io' => 'required|numeric|min:10|max:1000', 'cpu' => 'required|numeric|min:0', 'disk' => 'required|numeric|min:0', - 'service' => 'required|numeric|min:1|exists:services,id', - 'option' => 'required|numeric|min:1|exists:service_options,id', - 'pack' => 'sometimes|nullable|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', 'startup' => 'string', 'custom_image_name' => 'required_if:use_custom_image,on', 'auto_deploy' => 'sometimes|boolean', 'custom_id' => 'sometimes|required|numeric|unique:servers,id', ]); - $validator->sometimes('node', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) { + $validator->sometimes('node_id', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) { return ! ($input->auto_deploy); }); @@ -112,7 +112,7 @@ class ServerRepository return ! $input->auto_deploy && ! $input->allocation; }); - $validator->sometimes('allocation', 'numeric|exists:allocations,id', function ($input) { + $validator->sometimes('allocation_id', 'numeric|exists:allocations,id', function ($input) { return ! ($input->auto_deploy || ($input->port && $input->ip)); }); @@ -122,12 +122,7 @@ class ServerRepository throw new DisplayValidationException($validator->errors()); } - if (is_int($data['owner'])) { - $user = Models\User::select('id', 'email')->where('id', $data['owner'])->first(); - } else { - $user = Models\User::select('id', 'email')->where('email', $data['owner'])->first(); - } - + $user = Models\User::select('id', 'email')->where((is_int($data['owner'])) ? 'id' : 'email', $data['owner'])->first(); if (! $user) { throw new DisplayException('The user id or email passed to the function was not found on the system.'); } @@ -136,23 +131,24 @@ class ServerRepository if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) { // This is an auto-deployment situation // Ignore any other passed node data - unset($data['node'], $data['ip'], $data['port'], $data['allocation']); + unset($data['node_id'], $data['ip'], $data['port'], $data['allocation_id']); $autoDeployed = true; - $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location']); + $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location_id']); $allocation = DeploymentService::randomAllocation($node->id); } else { - $node = Models\Node::getByID($data['node']); + $node = Models\Node::findOrFail($data['node_id']); } // 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 (! $autoDeployed) { - if (! isset($data['allocation'])) { - $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first(); + if (! isset($data['allocation_id'])) { + $model = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port']); } else { - $allocation = Models\Allocation::where('id', $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first(); + $model = Models\Allocation::where('id', $data['allocation_id']); } + $allocation = $model->where('node_id', $data['node_id'])->whereNull('server_id')->first(); } // Something failed in the query, either that combo doesn't exist, or it is in use. @@ -164,28 +160,28 @@ class ServerRepository // 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 = Models\ServiceOptions::where('id', $data['option'])->where('parent_service', $data['service'])->first(); + $option = Models\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 ($data['pack'] == 0) { - $data['pack'] = null; + if ($data['pack_id'] == 0) { + $data['pack_id'] = null; } - if (! is_null($data['pack'])) { - $pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first(); + if (! is_null($data['pack_id'])) { + $pack = Models\ServicePack::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 = Models\Service::find($option->parent_service); + $service = Models\Service::find($option->service_id); // Check those Variables - $variables = Models\ServiceVariables::where('option_id', $data['option'])->get(); + $variables = Models\ServiceVariable::where('option_id', $data['option_id'])->get(); $variableList = []; if ($variables) { foreach ($variables as $variable) { @@ -220,7 +216,7 @@ class ServerRepository // Check Overallocation if (! $autoDeployed) { if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); + $totals = Models\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)) { @@ -259,20 +255,20 @@ class ServerRepository $server->fill([ 'uuid' => $genUuid, 'uuidShort' => $genShortUuid, - 'node' => $node->id, + 'node_id' => $node->id, 'name' => $data['name'], 'suspended' => 0, - 'owner' => $user->id, + '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'])) ? true : false, - 'allocation' => $allocation->id, - 'service' => $data['service'], - 'option' => $data['option'], - 'pack' => $data['pack'], + '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_image_name'])) ? $data['custom_image_name'] : $option->docker_image, @@ -282,7 +278,7 @@ class ServerRepository $server->save(); // Mark Allocation in Use - $allocation->assigned_to = $server->id; + $allocation->server_id = $server->id; $allocation->save(); // Add Variables @@ -293,28 +289,14 @@ class ServerRepository foreach ($variableList as $item) { $environmentVariables[$item['env']] = $item['val']; - Models\ServerVariables::create([ + Models\ServerVariable::create([ 'server_id' => $server->id, 'variable_id' => $item['id'], 'variable_value' => $item['val'], ]); } - // Queue Notification Email - $user->notify((new ServerCreated([ - 'name' => $server->name, - 'memory' => $server->memory, - 'node' => $node->name, - 'service' => $service->name, - 'option' => $option->name, - 'uuidShort' => $server->uuidShort, - ]))); - - $client = Models\Node::guzzleRequest($node->id); - $client->request('POST', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - ], + $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [ 'json' => [ 'uuid' => (string) $server->uuid, 'user' => $server->username, @@ -348,8 +330,8 @@ class ServerRepository DB::commit(); - return $server->id; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + return $server; + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex); } catch (\Exception $ex) { @@ -384,20 +366,19 @@ class ServerRepository DB::beginTransaction(); try { - $server = Models\Server::findOrFail($id); - $owner = Models\User::findOrFail($server->owner); + $server = Models\Server::with('user')->findOrFail($id); // Update daemon secret if it was passed. - if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $owner->email)) { + if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $server->user->email)) { $oldDaemonKey = $server->daemonSecret; $server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); $resetDaemonKey = true; } // Update Server Owner if it was passed. - if (isset($data['owner']) && $data['owner'] !== $owner->email) { + if (isset($data['owner']) && $data['owner'] !== $server->user->email) { $newOwner = Models\User::select('id')->where('email', $data['owner'])->first(); - $server->owner = $newOwner->id; + $server->owner_id = $newOwner->id; } // Update Server Name if it was passed. @@ -415,15 +396,10 @@ class ServerRepository return true; } - // If we need to update do it here. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $res = $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'exceptions' => false, 'json' => [ 'keys' => [ @@ -472,14 +448,10 @@ class ServerRepository $server->image = $data['image']; $server->save(); - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'build' => [ 'image' => $server->image, @@ -490,7 +462,7 @@ class ServerRepository DB::commit(); return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('An error occured while attempting to update the container image.', $ex); } catch (\Exception $ex) { @@ -530,27 +502,25 @@ class ServerRepository DB::beginTransaction(); try { - $server = Models\Server::findOrFail($id); - $allocation = Models\Allocation::findOrFail($server->allocation); - + $server = Models\Server::with('allocation', 'allocations')->findOrFail($id); $newBuild = []; if (isset($data['default'])) { list($ip, $port) = explode(':', $data['default']); - if ($ip !== $allocation->ip || (int) $port !== $allocation->port) { - $selection = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->first(); + if ($ip !== $server->allocation->ip || (int) $port !== $server->allocation->port) { + $selection = $server->allocations->where('ip', $ip)->where('port', $port)->first(); if (! $selection) { throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.'); } - $server->allocation = $selection->id; + $server->allocation_id = $selection->id; $newBuild['default'] = [ 'ip' => $ip, 'port' => (int) $port, ]; // Re-Run to keep updated for rest of function - $allocation = Models\Allocation::findOrFail($server->allocation); + $server->load('allocation'); } } @@ -565,15 +535,17 @@ class ServerRepository } // Can't remove the assigned IP/Port combo - if ($ip === $allocation->ip && (int) $port === (int) $allocation->port) { + if ($ip === $server->allocation->ip && (int) $port === (int) $server->allocation->port) { break; } $newPorts = true; - Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([ - 'assigned_to' => null, + $server->allocations->where('ip', $ip)->where('port', $port)->update([ + 'server_id' => null, ]); } + + $server->load('allocations'); } // Add Assignments @@ -586,21 +558,22 @@ class ServerRepository } // Don't allow double port assignments - if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) { + if ($server->allocations->where('port', $port)->count() !== 0) { break; } $newPorts = true; - Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([ - 'assigned_to' => $server->id, + Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('server_id')->update([ + 'server_id' => $server->id, ]); } + + $server->load('allocations'); } // Loop All Assignments $additionalAssignments = []; - $assignments = Models\Allocation::where('assigned_to', $server->id)->get(); - foreach ($assignments as &$assignment) { + foreach ($server->allocations as &$assignment) { if (array_key_exists((string) $assignment->ip, $additionalAssignments)) { array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port); } else { @@ -646,14 +619,10 @@ class ServerRepository $server->save(); if (! empty($newBuild)) { - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'build' => $newBuild, ], @@ -663,7 +632,7 @@ class ServerRepository DB::commit(); return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('An error occured while attempting to update the configuration.', $ex); } catch (\Exception $ex) { @@ -674,34 +643,34 @@ class ServerRepository public function updateStartup($id, array $data, $admin = false) { - $server = Models\Server::findOrFail($id); + $server = Models\Server::with('variables', 'option.variables')->findOrFail($id); DB::beginTransaction(); try { // Check the startup - if (isset($data['startup'])) { + if (isset($data['startup']) && $admin) { $server->startup = $data['startup']; $server->save(); } // Check those Variables - $variables = Models\ServiceVariables::select( - 'service_variables.*', - DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_currentValue') - )->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id') - ->where('option_id', $server->option) - ->get(); + $server->option->variables->transform(function ($item, $key) use ($server) { + $displayValue = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + $item->server_value = (! is_null($displayValue)) ? $displayValue : $item->default_value; + + return $item; + }); $variableList = []; - if ($variables) { - foreach ($variables as &$variable) { + if ($server->option->variables) { + foreach ($server->option->variables as &$variable) { // Move on if the new data wasn't even sent if (! isset($data[$variable->env_variable])) { $variableList[] = [ 'id' => $variable->id, 'env' => $variable->env_variable, - 'val' => $variable->a_currentValue, + 'val' => $variable->server_value, ]; continue; } @@ -719,13 +688,13 @@ class ServerRepository // Is the variable required? // @TODO: is this even logical to perform this check? if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) { - if ($variable->required === 1) { + if ($variable->required) { throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.'); } } // Variable hidden and/or not user editable - if (($variable->user_viewable === 0 || $variable->user_editable === 0) && ! $admin) { + if ((! $variable->user_viewable || ! $variable->user_editable) && ! $admin) { throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.'); } @@ -750,7 +719,7 @@ class ServerRepository $environmentVariables[$item['env']] = $item['val']; // Update model or make a new record if it doesn't exist. - $model = Models\ServerVariables::firstOrNew([ + $model = Models\ServerVariable::firstOrNew([ 'variable_id' => $item['id'], 'server_id' => $server->id, ]); @@ -758,14 +727,10 @@ class ServerRepository $model->save(); } - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'build' => [ 'env|overwrite' => $environmentVariables, @@ -776,7 +741,7 @@ class ServerRepository DB::commit(); return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('An error occured while attempting to update the server configuration.', $ex); } catch (\Exception $ex) { @@ -791,15 +756,14 @@ class ServerRepository DB::beginTransaction(); try { - if ($force === 'force' || $force === true) { + if ($force === 'force' || $force) { $server->installed = 3; $server->save(); } $server->delete(); - DB::commit(); - event(new ServerDeleted($server->id)); + return DB::commit(); } catch (\Exception $ex) { DB::rollBack(); throw $ex; @@ -808,8 +772,7 @@ class ServerRepository public function deleteNow($id, $force = false) { - $server = Models\Server::withTrashed()->findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Models\Server::withTrashed()->with('node')->findOrFail($id); // Handle server being restored previously or // an accidental queue. @@ -820,18 +783,20 @@ class ServerRepository DB::beginTransaction(); try { // Unassign Allocations - Models\Allocation::where('assigned_to', $server->id)->update([ - 'assigned_to' => null, + Models\Allocation::where('server_id', $server->id)->update([ + 'server_id' => null, ]); // Remove Variables - Models\ServerVariables::where('server_id', $server->id)->delete(); - - // Remove Permissions (Foreign Key requires before Subusers) - Models\Permission::where('server_id', $server->id)->delete(); + Models\ServerVariable::where('server_id', $server->id)->delete(); // Remove SubUsers - Models\Subuser::where('server_id', $server->id)->delete(); + foreach (Models\Subuser::with('permissions')->where('server_id', $server->id)->get() as &$subuser) { + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } + $subuser->delete(); + } // Remove Downloads Models\Download::where('server', $server->uuid)->delete(); @@ -847,17 +812,14 @@ class ServerRepository $repository->drop($database->id); } - $client = Models\Node::guzzleRequest($server->node); - $client->request('DELETE', '/servers', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ], - ]); + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('DELETE', '/servers'); $server->forceDelete(); DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { // Set installed is set to 3 when force deleting. if ($server->installed === 3 || $force) { $server->forceDelete(); @@ -887,7 +849,7 @@ class ServerRepository if ($server->installed === 2) { throw new DisplayException('This server was marked as having a failed install, you cannot override this.'); } - $server->installed = ($server->installed === 1) ? 0 : 1; + $server->installed = ! $server->installed; return $server->save(); } @@ -899,31 +861,27 @@ class ServerRepository */ public function suspend($id, $deleted = false) { - $server = ($deleted) ? Models\Server::withTrashed()->findOrFail($id) : Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Models\Server::withTrashed()->with('node')->findOrFail($id); DB::beginTransaction(); try { // Already suspended, no need to make more requests. - if ($server->suspended === 1) { + if ($server->suspended) { return true; } $server->suspended = 1; $server->save(); - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/suspend', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ], - ]); + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', '/server/suspend'); return DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex); } catch (\Exception $ex) { @@ -939,8 +897,7 @@ class ServerRepository */ public function unsuspend($id) { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Models\Server::with('node')->findOrFail($id); DB::beginTransaction(); @@ -954,16 +911,13 @@ class ServerRepository $server->suspended = 0; $server->save(); - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/unsuspend', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ], - ]); + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', '/server/unsuspend'); return DB::commit(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex); } catch (\Exception $ex) { @@ -974,12 +928,9 @@ class ServerRepository public function updateSFTPPassword($id, $password) { - $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); + $server = Models\Server::with('node')->findOrFail($id); - $validator = Validator::make([ - 'password' => $password, - ], [ + $validator = Validator::make(['password' => $password], [ 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/', ]); @@ -993,21 +944,17 @@ class ServerRepository try { $server->save(); - $client = Models\Node::guzzleRequest($server->node); - $client->request('POST', '/server/password', [ - 'headers' => [ - 'X-Access-Token' => $node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ], - 'json' => [ - 'password' => $password, - ], + $server->node->guzzleClient([ + 'X-Access-Token' => $server->node->daemonSecret, + 'X-Access-Server' => $server->uuid, + ])->request('POST', '/server/password', [ + 'json' => ['password' => $password], ]); DB::commit(); return true; - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex); } catch (\Exception $ex) { diff --git a/app/Repositories/ServiceRepository/Option.php b/app/Repositories/ServiceRepository/Option.php index 8ddcdc006..e0b82d347 100644 --- a/app/Repositories/ServiceRepository/Option.php +++ b/app/Repositories/ServiceRepository/Option.php @@ -62,8 +62,8 @@ class Option $data['startup'] = null; } - $option = new Models\ServiceOptions; - $option->parent_service = $service->id; + $option = new Models\ServiceOption; + $option->service_id = $service->id; $option->fill($data); $option->save(); @@ -72,7 +72,7 @@ class Option public function delete($id) { - $option = Models\ServiceOptions::findOrFail($id); + $option = Models\ServiceOption::findOrFail($id); $servers = Models\Server::where('option', $option->id)->get(); if (count($servers) !== 0) { @@ -82,7 +82,7 @@ class Option DB::beginTransaction(); try { - Models\ServiceVariables::where('option_id', $option->id)->delete(); + Models\ServiceVariable::where('option_id', $option->id)->delete(); $option->delete(); DB::commit(); @@ -94,7 +94,7 @@ class Option public function update($id, array $data) { - $option = Models\ServiceOptions::findOrFail($id); + $option = Models\ServiceOption::findOrFail($id); $validator = Validator::make($data, [ 'name' => 'sometimes|required|string|max:255', diff --git a/app/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php index ae68c1a6b..2132c4954 100644 --- a/app/Repositories/ServiceRepository/Pack.php +++ b/app/Repositories/ServiceRepository/Pack.php @@ -49,12 +49,6 @@ class Pack 'option' => 'required|exists:service_options,id', 'selectable' => 'sometimes|boolean', 'visible' => 'sometimes|boolean', - 'build_memory' => 'required|integer|min:0', - 'build_swap' => 'required|integer|min:0', - 'build_cpu' => 'required|integer|min:0', - 'build_io' => 'required|integer|min:10|max:1000', - 'build_container' => 'required|string', - 'build_script' => 'sometimes|nullable|string', ]); if ($validator->fails()) { @@ -66,11 +60,8 @@ class Pack throw new DisplayException('The file provided does not appear to be valid.'); } - if (! in_array($data['file_upload']->getMimeType(), [ - 'application/zip', - 'application/gzip', - ])) { - throw new DisplayException('The file provided does not meet the required filetypes of application/zip or application/gzip.'); + 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.'); } } @@ -78,14 +69,8 @@ class Pack try { $uuid = new UuidService; $pack = Models\ServicePack::create([ - 'option' => $data['option'], - 'uuid' => $uuid->generate('servers', 'uuid'), - 'build_memory' => $data['build_memory'], - 'build_swap' => $data['build_swap'], - 'build_cpu' => $data['build_swap'], - 'build_io' => $data['build_io'], - 'build_script' => (empty($data['build_script'])) ? null : $data['build_script'], - 'build_container' => $data['build_container'], + 'option_id' => $data['option'], + 'uuid' => $uuid->generate('service_packs', 'uuid'), 'name' => $data['name'], 'version' => $data['version'], 'description' => (empty($data['description'])) ? null : $data['description'], @@ -95,8 +80,7 @@ class Pack Storage::makeDirectory('packs/' . $pack->uuid); if (isset($data['file_upload'])) { - $filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz'; - $data['file_upload']->storeAs('packs/' . $pack->uuid, $filename); + $data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); } DB::commit(); @@ -105,7 +89,7 @@ class Pack throw $ex; } - return $pack->id; + return $pack; } public function createWithTemplate(array $data) @@ -132,38 +116,30 @@ class Pack throw new DisplayException('The uploaded archive was unable to be opened.'); } - $isZip = $zip->locateName('archive.zip'); $isTar = $zip->locateName('archive.tar.gz'); - if ($zip->locateName('import.json') === false || ($isZip === false && $isTar === false)) { + 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')); - $id = $this->create([ + $pack = $this->create([ 'name' => $json->name, 'version' => $json->version, 'description' => $json->description, 'option' => $data['option'], 'selectable' => $json->selectable, 'visible' => $json->visible, - 'build_memory' => $json->build->memory, - 'build_swap' => $json->build->swap, - 'build_cpu' => $json->build->cpu, - 'build_io' => $json->build->io, - 'build_container' => $json->build->container, - 'build_script' => $json->build->script, ]); - $pack = Models\ServicePack::findOrFail($id); - if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), ($isZip === false) ? 'archive.tar.gz' : 'archive.zip')) { + 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->id; + return $pack; } else { $json = json_decode(file_get_contents($data['file_upload']->path())); @@ -174,12 +150,6 @@ class Pack 'option' => $data['option'], 'selectable' => $json->selectable, 'visible' => $json->visible, - 'build_memory' => $json->build->memory, - 'build_swap' => $json->build->swap, - 'build_cpu' => $json->build->cpu, - 'build_io' => $json->build->io, - 'build_container' => $json->build->container, - 'build_script' => $json->build->script, ]); } } @@ -193,36 +163,20 @@ class Pack 'option' => 'required|exists:service_options,id', 'selectable' => 'sometimes|boolean', 'visible' => 'sometimes|boolean', - 'build_memory' => 'required|integer|min:0', - 'build_swap' => 'required|integer|min:0', - 'build_cpu' => 'required|integer|min:0', - 'build_io' => 'required|integer|min:10|max:1000', - 'build_container' => 'required|string', - 'build_script' => 'sometimes|string', ]); if ($validator->fails()) { throw new DisplayValidationException($validator->errors()); } - DB::transaction(function () use ($id, $data) { - Models\ServicePack::findOrFail($id)->update([ - 'option' => $data['option'], - 'build_memory' => $data['build_memory'], - 'build_swap' => $data['build_swap'], - 'build_cpu' => $data['build_swap'], - 'build_io' => $data['build_io'], - 'build_script' => (empty($data['build_script'])) ? null : $data['build_script'], - 'build_container' => $data['build_container'], - 'name' => $data['name'], - 'version' => $data['version'], - 'description' => (empty($data['description'])) ? null : $data['description'], - 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']), - ]); - - return true; - }); + Models\ServicePack::findOrFail($id)->update([ + 'option_id' => $data['option'], + 'name' => $data['name'], + 'version' => $data['version'], + 'description' => (empty($data['description'])) ? null : $data['description'], + 'selectable' => isset($data['selectable']), + 'visible' => isset($data['visible']), + ]); } public function delete($id) diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index eb41a9fee..206479a0a 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -53,25 +53,22 @@ class Service throw new DisplayValidationException($validator->errors()); } - $data['author'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4)); - - $service = new Models\Service; DB::beginTransaction(); - try { + $service = new Models\Service; + $service->author = env('SERVICE_AUTHOR', (string) Uuid::generate(4)); $service->fill($data); $service->save(); - Storage::put('services/' . $data['file'] . '/main.json', '{}'); - Storage::copy('services/.templates/index.js', 'services/' . $data['file'] . '/index.js'); - + Storage::put('services/' . $service->file . '/main.json', '{}'); + Storage::copy('services/.templates/index.js', 'services/' . $service->file . '/index.js'); DB::commit(); } catch (\Exception $ex) { DB::rollBack(); throw $ex; } - return $service->id; + return $service; } public function update($id, array $data) @@ -99,7 +96,7 @@ class Service { $service = Models\Service::findOrFail($id); $servers = Models\Server::where('service', $service->id)->get(); - $options = Models\ServiceOptions::select('id')->where('parent_service', $service->id); + $options = Models\ServiceOption::select('id')->where('service_id', $service->id); if (count($servers) !== 0) { throw new DisplayException('You cannot delete a service that has servers associated with it.'); @@ -107,7 +104,7 @@ class Service DB::beginTransaction(); try { - Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); + Models\ServiceVariable::whereIn('option_id', $options->get()->toArray())->delete(); $options->delete(); $service->delete(); diff --git a/app/Repositories/ServiceRepository/Variable.php b/app/Repositories/ServiceRepository/Variable.php index fbd258444..f9836ff0f 100644 --- a/app/Repositories/ServiceRepository/Variable.php +++ b/app/Repositories/ServiceRepository/Variable.php @@ -39,16 +39,16 @@ class Variable public function create($id, array $data) { - $option = Models\ServiceOptions::findOrFail($id); + $option = Models\ServiceOption::select('id')->findOrFail($id); $validator = Validator::make($data, [ 'name' => 'required|string|min:1|max:255', 'description' => 'required|string', 'env_variable' => 'required|regex:/^[\w]{1,255}$/', 'default_value' => 'string|max:255', - 'user_viewable' => 'sometimes|required|numeric|size:1', - 'user_editable' => 'sometimes|required|numeric|size:1', - 'required' => 'sometimes|required|numeric|size:1', + 'user_viewable' => 'sometimes|required|nullable|boolean', + 'user_editable' => 'sometimes|required|nullable|boolean', + 'required' => 'sometimes|required|nullable|boolean', 'regex' => 'required|string|min:1', ]); @@ -60,28 +60,29 @@ class Variable throw new DisplayException('The default value you entered cannot violate the regex requirements.'); } - if (Models\ServiceVariables::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) { + if (Models\ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) { throw new DisplayException('An environment variable with that name already exists for this option.'); } $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0; $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0; $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0; + $data['option_id'] = $option->id; - $variable = new Models\ServiceVariables; - $variable->option_id = $option->id; - $variable->fill($data); + $variable = Models\ServiceVariable::create($data); - return $variable->save(); + return $variable; } public function delete($id) { - $variable = Models\ServiceVariables::findOrFail($id); + $variable = Models\ServiceVariable::with('serverVariable')->findOrFail($id); DB::beginTransaction(); try { - Models\ServerVariables::where('variable_id', $variable->id)->delete(); + foreach ($variable->serverVariable as $svar) { + $svar->delete(); + } $variable->delete(); DB::commit(); @@ -93,16 +94,16 @@ class Variable public function update($id, array $data) { - $variable = Models\ServiceVariables::findOrFail($id); + $variable = Models\ServiceVariable::findOrFail($id); $validator = Validator::make($data, [ 'name' => 'sometimes|required|string|min:1|max:255', 'description' => 'sometimes|required|string', 'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/', 'default_value' => 'sometimes|string|max:255', - 'user_viewable' => 'sometimes|required|numeric|boolean', - 'user_editable' => 'sometimes|required|numeric|boolean', - 'required' => 'sometimes|required|numeric|boolean', + 'user_viewable' => 'sometimes|required|nullable|boolean', + 'user_editable' => 'sometimes|required|nullable|boolean', + 'required' => 'sometimes|required|nullable|boolean', 'regex' => 'sometimes|required|string|min:1', ]); @@ -117,7 +118,7 @@ class Variable throw new DisplayException('The default value you entered cannot violate the regex requirements.'); } - if (Models\ServiceVariables::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) { + if (Models\ServiceVariable::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) { throw new DisplayException('An environment variable with that name already exists for this option.'); } @@ -125,7 +126,18 @@ class Variable $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable; $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required; - $variable->fill($data); + // Not using $data because the function that passes into this function + // can't do $requst->only() due to the page setup. + $variable->fill([ + 'name' => $data['name'], + 'description' => $data['description'], + 'env_variable' => $data['env_variable'], + 'default_value' => $data['default_value'], + 'user_viewable' => $data['user_viewable'], + 'user_editable' => $data['user_editable'], + 'required' => $data['required'], + 'regex' => $data['regex'], + ]); return $variable->save(); } diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/SubuserRepository.php index afa27493e..2d9da4b29 100644 --- a/app/Repositories/SubuserRepository.php +++ b/app/Repositories/SubuserRepository.php @@ -25,8 +25,6 @@ namespace Pterodactyl\Repositories; use DB; -use Mail; -use Settings; use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; @@ -112,11 +110,11 @@ class SubuserRepository * @param array $data * @throws DisplayValidationException * @throws DisplayException - * @return int Returns the ID of the newly created subuser. + * @return \Pterodactyl\Models\Subuser */ public function create($sid, array $data) { - $server = Models\Server::findOrFail($sid); + $server = Models\Server::with('node')->findOrFail($sid); $validator = Validator::make($data, [ 'permissions' => 'required|array', @@ -135,32 +133,28 @@ class SubuserRepository if (! $user) { try { $repo = new UserRepository; - $uid = $repo->create([ + $user = $repo->create([ 'email' => $data['email'], - 'username' => substr(str_replace('@', '', $data['email']), 0, 8), - 'name_first' => 'John', - 'name_last' => 'Doe', + 'username' => str_random(8), + 'name_first' => 'Unassigned', + 'name_last' => 'Name', 'root_admin' => false, ]); - $user = Models\User::findOrFail($uid); } catch (\Exception $ex) { throw $ex; } - } elseif ($server->owner === $user->id) { + } elseif ($server->owner_id === $user->id) { throw new DisplayException('You cannot add the owner of a server as a subuser.'); } elseif (Models\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 = new Models\Subuser; - $subuser->fill([ + $subuser = Models\Subuser::create([ 'user_id' => $user->id, 'server_id' => $server->id, 'daemonSecret' => (string) $uuid->generate('servers', 'uuid'), ]); - $subuser->save(); $daemonPermissions = $this->coreDaemonPermissions; foreach ($data['permissions'] as $permission) { @@ -170,13 +164,10 @@ class SubuserRepository array_push($daemonPermissions, $this->permissions[$permission]); } - $model = new Models\Permission; - $model->fill([ - 'user_id' => $user->id, - 'server_id' => $server->id, + Models\Permission::create([ + 'subuser_id' => $subuser->id, 'permission' => $permission, ]); - $model->save(); } } @@ -184,14 +175,10 @@ class SubuserRepository // We contact even if they don't have any daemon permissions to overwrite // if they did have them previously. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ $subuser->daemonSecret => $daemonPermissions, @@ -199,18 +186,9 @@ class SubuserRepository ], ]); - $email = $data['email']; - Mail::queue('emails.added-subuser', [ - 'serverName' => $server->name, - 'url' => route('server.index', $server->uuidShort), - ], function ($message) use ($email) { - $message->to($email); - $message->from(Settings::get('email_from', env('MAIL_FROM')), Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel'))); - $message->subject(Settings::get('company') . ' - Added to Server'); - }); DB::commit(); - return $subuser->id; + return $subuser; } catch (\GuzzleHttp\Exception\TransferException $ex) { DB::rollBack(); throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex); @@ -232,22 +210,16 @@ class SubuserRepository */ public function delete($id) { - $subuser = Models\Subuser::findOrFail($id); - $server = Models\Server::findOrFail($subuser->server_id); + $subuser = Models\Subuser::with('server.node')->findOrFail($id); + $server = $subuser->server; DB::beginTransaction(); try { - Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); - - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ $subuser->daemonSecret => [], @@ -255,6 +227,9 @@ class SubuserRepository ], ]); + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } $subuser->delete(); DB::commit(); @@ -290,13 +265,15 @@ class SubuserRepository throw new DisplayValidationException(json_encode($validator->all())); } - $subuser = Models\Subuser::findOrFail($id); - $server = Models\Server::findOrFail($data['server']); + $subuser = Models\Subuser::with('server.node')->findOrFail($id); + $server = $subuser->server; DB::beginTransaction(); try { - Models\Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } $daemonPermissions = $this->coreDaemonPermissions; foreach ($data['permissions'] as $permission) { @@ -305,27 +282,20 @@ class SubuserRepository if (! is_null($this->permissions[$permission])) { array_push($daemonPermissions, $this->permissions[$permission]); } - $model = new Models\Permission; - $model->fill([ - 'user_id' => $data['user'], - 'server_id' => $data['server'], + Models\Permission::create([ + 'subuser_id' => $subuser->id, 'permission' => $permission, ]); - $model->save(); } } // Contact Daemon // We contact even if they don't have any daemon permissions to overwrite // if they did have them previously. - $node = Models\Node::getByID($server->node); - $client = Models\Node::guzzleRequest($server->node); - - $res = $client->request('PATCH', '/server', [ - 'headers' => [ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $node->daemonSecret, - ], + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ 'json' => [ 'keys' => [ $subuser->daemonSecret => $daemonPermissions, diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index b46727e1b..e9cd8580f 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -34,7 +34,6 @@ use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Notifications\AccountCreated; use Pterodactyl\Exceptions\DisplayValidationException; class UserRepository @@ -105,13 +104,11 @@ class UserRepository 'token' => $token, 'created_at' => Carbon::now()->toDateTimeString(), ]); - - $user->notify((new AccountCreated($token))); } DB::commit(); - return $user->id; + return $user; } catch (\Exception $ex) { DB::rollBack(); throw $ex; @@ -167,7 +164,7 @@ class UserRepository */ public function delete($id) { - if (Models\Server::where('owner', $id)->count() > 0) { + if (Models\Server::where('owner_id', $id)->count() > 0) { throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); } @@ -179,10 +176,15 @@ class UserRepository DB::beginTransaction(); try { - Models\Permission::where('user_id', $id)->delete(); - Models\Subuser::where('user_id', $id)->delete(); - Models\User::destroy($id); + foreach (Models\Subuser::with('permissions')->where('user_id', $id)->get() as &$subuser) { + foreach ($subuser->permissions as &$permission) { + $permission->delete(); + } + $subuser->delete(); + } + + Models\User::destroy($id); DB::commit(); return true; diff --git a/app/Services/DeploymentService.php b/app/Services/DeploymentService.php index 5f2a99473..af1ede940 100644 --- a/app/Services/DeploymentService.php +++ b/app/Services/DeploymentService.php @@ -65,7 +65,7 @@ class DeploymentService throw new DisplayException('The location passed was not valid and could not be found.'); } - $node = Models\Node::where('location', $useLocation->id)->where('public', 1)->whereNotIn('id', $not)->inRandomOrder()->first(); + $node = Models\Node::where('location_id', $useLocation->id)->where('public', 1)->whereNotIn('id', $not)->inRandomOrder()->first(); if (! $node) { throw new DisplayException("Unable to find a node in location {$useLocation->short} (id: {$useLocation->id}) that is available and has space."); } @@ -107,7 +107,7 @@ class DeploymentService */ public static function randomAllocation($node) { - $allocation = Models\Allocation::where('node', $node)->whereNull('assigned_to')->inRandomOrder()->first(); + $allocation = Models\Allocation::where('node_id', $node)->whereNull('server_id')->inRandomOrder()->first(); if (! $allocation) { throw new DisplayException('No available allocation could be found for the assigned node.'); } @@ -125,7 +125,7 @@ class DeploymentService protected static function checkNodeAllocation(Models\Node $node, $memory, $disk) { if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(); + $totals = Models\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)) { diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index bb76db9b6..7912cf65d 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Services; -use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Notifications\Daemon; @@ -48,7 +47,6 @@ class NotificationService public function __construct(Server $server) { $this->server = $server; - $this->user = User::findOrFail($server->owner); } public function pass(array $notification) diff --git a/app/Services/VersionService.php b/app/Services/VersionService.php index d12c77760..dea0bb16d 100644 --- a/app/Services/VersionService.php +++ b/app/Services/VersionService.php @@ -52,6 +52,7 @@ class VersionService return (object) [ 'panel' => 'error', 'daemon' => 'error', + 'discord' => 'https://pterodactyl.io', ]; } }); @@ -67,6 +68,11 @@ class VersionService return self::$versions->daemon; } + public static function getDiscord() + { + return self::$versions->discord; + } + public function getCurrentPanel() { return config('app.version'); diff --git a/composer.json b/composer.json index 902eb86e6..40bee8bcb 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "dingo/api": "1.0.0-beta6", "aws/aws-sdk-php": "3.19.20", "predis/predis": "1.1.1", + "fideloper/proxy": "3.2.0", "laracasts/utilities": "2.1.0", "lord/laroute": "2.3.0" }, @@ -60,27 +61,18 @@ ], "post-install-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", - "php artisan optimize" + "php artisan optimize", + "php artisan config:cache" ], "post-update-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postUpdate", - "php artisan optimize" - ], - "setup-dev": [ - "composer install", - "php -r \"copy('.env.example', '.env');\"", - "php vendor/bin/homestead make --ip=192.168.10.32", - "sed -i.bak 's/homestead.app/pterodactyl.local/g' Homestead.yaml", - "rm Homestead.yaml.bak", - "php artisan key:generate" + "php artisan optimize", + "php artisan config:cache" ], "setup": [ "composer install --ansi --no-dev", "php -r \"file_exists('.env') || copy('.env.example', '.env');\"", "php artisan key:generate" - ], - "upgrade": [ - "composer update --ansi --no-dev" ] }, "config": { diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..22e5c4e3b --- /dev/null +++ b/composer.lock @@ -0,0 +1,5141 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "49714983a18ad2bba4759ccde45314c5", + "content-hash": "af8cd5b69f96dd17c1e02afc1ba8e467", + "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.19.20", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "36a05623d5f7f4a001cd3396ce0f000262c75062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/36a05623d5f7f4a001cd3396ce0f000262c75062", + "reference": "36a05623d5f7f4a001cd3396ce0f000262c75062", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^5.3.1|^6.2.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.3.1", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "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.0|~5.0", + "psr/cache": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2016-10-25 19:23:39" + }, + { + "name": "barryvdh/laravel-debugbar", + "version": "V2.2.3", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "ecd1ce5c4a827e2f6a8fb41bcf67713beb1c1cbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/ecd1ce5c4a827e2f6a8fb41bcf67713beb1c1cbd", + "reference": "ecd1ce5c4a827e2f6a8fb41bcf67713beb1c1cbd", + "shasum": "" + }, + "require": { + "illuminate/support": "5.1.*|5.2.*|5.3.*", + "maximebf/debugbar": "~1.11.0|~1.12.0", + "php": ">=5.5.9", + "symfony/finder": "~2.7|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "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": "2016-07-29 15:00:36" + }, + { + "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": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "description": "Base32 encoder/decoder according to RFC 4648", + "homepage": "https://github.com/ChristianRiesen/base32", + "keywords": [ + "base32", + "decode", + "encode", + "rfc4648" + ], + "time": "2016-05-05 11:49:03" + }, + { + "name": "classpreloader/classpreloader", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/ClassPreloader/ClassPreloader.git", + "reference": "bc7206aa892b5a33f4680421b69b191efd32b096" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/bc7206aa892b5a33f4680421b69b191efd32b096", + "reference": "bc7206aa892b5a33f4680421b69b191efd32b096", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.0|^2.0|^3.0", + "php": ">=5.5.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "ClassPreloader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": [ + "autoload", + "class", + "preload" + ], + "time": "2016-09-16 12:50:15" + }, + { + "name": "dingo/api", + "version": "v1.0.0-beta6", + "source": { + "type": "git", + "url": "https://github.com/dingo/api.git", + "reference": "cac67ab05da38c3de6d48c7146b700a13ed54cd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dingo/api/zipball/cac67ab05da38c3de6d48c7146b700a13ed54cd0", + "reference": "cac67ab05da38c3de6d48c7146b700a13ed54cd0", + "shasum": "" + }, + "require": { + "dingo/blueprint": "0.2.*", + "doctrine/annotations": "1.2.*", + "illuminate/routing": "5.1.* || 5.2.* || 5.3.*", + "illuminate/support": "5.1.* || 5.2.* || 5.3.*", + "league/fractal": ">=0.12.0", + "php": "^5.5.9 || ^7.0" + }, + "require-dev": { + "illuminate/auth": "5.1.* || 5.2.* || 5.3.*", + "illuminate/cache": "5.1.* || 5.2.* || 5.3.*", + "illuminate/console": "5.1.* || 5.2.* || 5.3.*", + "illuminate/database": "5.1.* || 5.2.* || 5.3.*", + "illuminate/events": "5.1.* || 5.2.* || 5.3.*", + "illuminate/filesystem": "5.1.* || 5.2.* || 5.3.*", + "illuminate/log": "5.1.* || 5.2.* || 5.3.*", + "illuminate/pagination": "5.1.* || 5.2.* || 5.3.*", + "laravel/lumen-framework": "5.1.* || 5.2.*", + "lucadegasperi/oauth2-server-laravel": "5.0.*", + "mockery/mockery": "~0.9", + "phpunit/phpunit": "^4.8 || ^5.0", + "squizlabs/php_codesniffer": "~2.0", + "tymon/jwt-auth": "1.0.*" + }, + "suggest": { + "lucadegasperi/oauth2-server-laravel": "Protect your API with OAuth 2.0.", + "tymon/jwt-auth": "Protect your API with JSON Web Tokens." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Dingo\\Api\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jason Lewis", + "email": "jason.lewis1991@gmail.com" + } + ], + "description": "A RESTful API package for the Laravel and Lumen frameworks.", + "keywords": [ + "api", + "dingo", + "laravel", + "restful" + ], + "time": "2016-08-30 03:57:04" + }, + { + "name": "dingo/blueprint", + "version": "0.2.2", + "source": { + "type": "git", + "url": "https://github.com/dingo/blueprint.git", + "reference": "690bdf0f76b4428fd52835b9d778fb4551333867" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dingo/blueprint/zipball/690bdf0f76b4428fd52835b9d778fb4551333867", + "reference": "690bdf0f76b4428fd52835b9d778fb4551333867", + "shasum": "" + }, + "require": { + "doctrine/annotations": "~1.2", + "illuminate/filesystem": "5.1.* || 5.2.* || 5.3.* || 5.4.*", + "illuminate/support": "5.1.* || 5.2.* || 5.3.* || 5.4.*", + "php": ">=5.5.9", + "phpdocumentor/reflection-docblock": "3.1.*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.2-dev" + } + }, + "autoload": { + "psr-4": { + "Dingo\\Blueprint\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jason Lewis", + "email": "jason.lewis1991@gmail.com" + } + ], + "description": "API Blueprint documentation generator.", + "keywords": [ + "api", + "blueprint", + "dingo", + "docs", + "laravel" + ], + "time": "2017-02-11 17:28:57" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "project", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "time": "2014-10-24 07:27:01" + }, + { + "name": "doctrine/annotations", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Annotations\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2015-08-31 12:32:49" + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2016-10-29 11:16:17" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03 10:49:41" + }, + { + "name": "doctrine/common", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/7bce00698899aa2c06fe7365c76e4d78ddb15fa3", + "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.5|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2016-11-30 16:50:46" + }, + { + "name": "doctrine/dbal", + "version": "v2.5.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "9f8c05cd5225a320d56d4bfdb4772f10d045a0c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/9f8c05cd5225a320d56d4bfdb4772f10d045a0c9", + "reference": "9f8c05cd5225a320d56d4bfdb4772f10d045a0c9", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.7-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2016-09-09 19:13:33" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2015-11-06 14:35:42" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09 13:34:57" + }, + { + "name": "edvinaskrucas/settings", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/edvinaskrucas/settings.git", + "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/edvinaskrucas/settings/zipball/23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", + "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", + "shasum": "" + }, + "require": { + "illuminate/console": "^5.2", + "illuminate/database": "^5.2", + "illuminate/filesystem": "^5.2", + "illuminate/support": "^5.2", + "php": "^5.5|^7.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Krucas\\Settings\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edvinas Kručas", + "email": "edv.krucas@gmail.com" + } + ], + "description": "Persistent settings package for Laravel framework.", + "keywords": [ + "Settings", + "laravel", + "persistent settings" + ], + "time": "2016-01-19 13:50:39" + }, + { + "name": "fideloper/proxy", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/fideloper/TrustedProxy.git", + "reference": "b270bfa52915e55cfb67faa0f16863a479c36121" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/b270bfa52915e55cfb67faa0f16863a479c36121", + "reference": "b270bfa52915e55cfb67faa0f16863a479c36121", + "shasum": "" + }, + "require": { + "illuminate/contracts": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/http": "~5.0", + "mockery/mockery": "~0.9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Fideloper\\Proxy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Fidao", + "email": "fideloper@gmail.com" + } + ], + "description": "Set trusted proxies for Laravel", + "keywords": [ + "load balancing", + "proxy", + "trusted proxy" + ], + "time": "2016-12-20 14:23:22" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.3.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2016-10-08 15:01:37" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20 10:07:11" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", + "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "time": "2016-06-24 23:00:38" + }, + { + "name": "igaster/laravel-theme", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/igaster/laravel-theme.git", + "reference": "d1b504b80e45a9fb40dffa1ae3d34ea364691da1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igaster/laravel-theme/zipball/d1b504b80e45a9fb40dffa1ae3d34ea364691da1", + "reference": "d1b504b80e45a9fb40dffa1ae3d34ea364691da1", + "shasum": "" + }, + "require": { + "illuminate/support": ">=5.2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/database": "~5.2", + "orchestra/testbench": "~3.0", + "phpunit/phpunit": "~4.0", + "vlucas/phpdotenv": "~2.0" + }, + "suggest": { + "orchestra/asset": "Use '@css' and '@js' in Blade files" + }, + "type": "library", + "autoload": { + "psr-4": { + "igaster\\laravelTheme\\": "src/", + "igaster\\laravelTheme\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Giannis Gasteratos", + "email": "igasteratos@gmail.com" + } + ], + "description": "Laravel 5 Themes: Asset & Views folder per theme. Theme inheritance. Blade integration and more...", + "homepage": "https://github.com/igaster/laravel-theme", + "keywords": [ + "assets", + "blade", + "laravel-5", + "package", + "themes", + "views" + ], + "time": "2016-06-04 07:19:01" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "0.*", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleColor": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com", + "homepage": "http://www.acci.cz" + } + ], + "time": "2014-04-08 15:00:19" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "time": "2015-04-20 18:58:01" + }, + { + "name": "jeremeamia/SuperClosure", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/jeremeamia/super_closure.git", + "reference": "443c3df3207f176a1b41576ee2a66968a507b3db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/443c3df3207f176a1b41576ee2a66968a507b3db", + "reference": "443c3df3207f176a1b41576ee2a66968a507b3db", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0", + "php": ">=5.4", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "SuperClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia", + "role": "Developer" + } + ], + "description": "Serialize Closure objects, including their context and binding", + "homepage": "https://github.com/jeremeamia/super_closure", + "keywords": [ + "closure", + "function", + "lambda", + "parser", + "serializable", + "serialize", + "tokenizer" + ], + "time": "2016-12-07 09:37:55" + }, + { + "name": "laracasts/utilities", + "version": "2.1", + "source": { + "type": "git", + "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", + "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laracasts\\Utilities\\JavaScript\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeffrey Way", + "email": "jeffrey@laracasts.com" + } + ], + "description": "Transform your PHP to JavaScript", + "keywords": [ + "javascript", + "laravel" + ], + "time": "2015-10-01 05:16:28" + }, + { + "name": "laravel/framework", + "version": "v5.3.21", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "140ad823863d5cc6f4580f1cdf9b18b9a6a457f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/140ad823863d5cc6f4580f1cdf9b18b9a6a457f4", + "reference": "140ad823863d5cc6f4580f1cdf9b18b9a6a457f4", + "shasum": "" + }, + "require": { + "classpreloader/classpreloader": "~3.0", + "doctrine/inflector": "~1.0", + "ext-mbstring": "*", + "ext-openssl": "*", + "jeremeamia/superclosure": "~2.2", + "league/flysystem": "~1.0", + "monolog/monolog": "~1.11", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "~1.20", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.6.4", + "psy/psysh": "0.7.*", + "ramsey/uuid": "~3.0", + "swiftmailer/swiftmailer": "~5.1", + "symfony/console": "3.1.*", + "symfony/debug": "3.1.*", + "symfony/finder": "3.1.*", + "symfony/http-foundation": "3.1.*", + "symfony/http-kernel": "3.1.*", + "symfony/process": "3.1.*", + "symfony/routing": "3.1.*", + "symfony/translation": "3.1.*", + "symfony/var-dumper": "3.1.*", + "vlucas/phpdotenv": "~2.2" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "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", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "tightenco/collect": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "mockery/mockery": "~0.9.4", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~5.4", + "predis/predis": "~1.0", + "symfony/css-selector": "3.1.*", + "symfony/dom-crawler": "3.1.*" + }, + "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.4).", + "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 (~5.3|~6.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~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.1.*).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (3.1.*).", + "symfony/psr-http-message-bridge": "Required to use psr7 bridging features (0.2.*)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "time": "2016-10-26 13:27:05" + }, + { + "name": "league/flysystem", + "version": "1.0.35", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "dda7f3ab94158a002d9846a97dc18ebfb7acc062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/dda7f3ab94158a002d9846a97dc18ebfb7acc062", + "reference": "dda7f3ab94158a002d9846a97dc18ebfb7acc062", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "ext-fileinfo": "*", + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^2.2", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "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-dropbox": "Allows you to use Dropbox 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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2017-02-09 11:33:58" + }, + { + "name": "league/fractal", + "version": "0.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/fractal.git", + "reference": "0546bd5c2aba414ba50c6ac676f507d42c6150a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/0546bd5c2aba414ba50c6ac676f507d42c6150a5", + "reference": "0546bd5c2aba414ba50c6ac676f507d42c6150a5", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "illuminate/contracts": "~5.0", + "mockery/mockery": "~0.9", + "pagerfanta/pagerfanta": "~1.0.0", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5", + "zendframework/zend-paginator": "~2.3" + }, + "suggest": { + "illuminate/pagination": "The Illuminate Pagination component.", + "pagerfanta/pagerfanta": "Pagerfanta Paginator", + "zendframework/zend-paginator": "Zend Framework Paginator" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.13-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Fractal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Sturgeon", + "email": "me@philsturgeon.uk", + "homepage": "http://philsturgeon.uk/", + "role": "Developer" + } + ], + "description": "Handle the output of complex data structures ready for API output.", + "homepage": "http://fractal.thephpleague.com/", + "keywords": [ + "api", + "json", + "league", + "rest" + ], + "time": "2016-12-28 01:30:30" + }, + { + "name": "lord/laroute", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/aaronlord/laroute.git", + "reference": "97a3812af4bcc4cad87c52c142f407c270e20f09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aaronlord/laroute/zipball/97a3812af4bcc4cad87c52c142f407c270e20f09", + "reference": "97a3812af4bcc4cad87c52c142f407c270e20f09", + "shasum": "" + }, + "require": { + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lord\\Laroute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Lord", + "email": "hello@aaronlord.is" + } + ], + "description": "Access Laravels URL/Route helper functions, from JavaScript.", + "keywords": [ + "javascript", + "laravel", + "routes", + "routing" + ], + "time": "2016-08-12 13:38:39" + }, + { + "name": "maximebf/debugbar", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "e634fbd32cd6bc3fa0e8c972b52d4bf49bab3988" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e634fbd32cd6bc3fa0e8c972b52d4bf49bab3988", + "reference": "e634fbd32cd6bc3fa0e8c972b52d4bf49bab3988", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-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": "2016-05-15 13:11:34" + }, + { + "name": "monolog/monolog", + "version": "1.22.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2016-11-26 00:15:39" + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5", + "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Cron": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "time": "2016-01-26 21:23:30" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2016-12-03 22:08:25" + }, + { + "name": "nesbot/carbon", + "version": "1.21.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7", + "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/translation": "~2.6|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2015-11-04 20:07:17" + }, + { + "name": "nikic/php-parser", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2016-09-16 12:04:44" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", + "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2016-11-07 23:38:38" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "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-30 07:12:33" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25 06:54:22" + }, + { + "name": "pragmarx/google2fa", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", + "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", + "shasum": "" + }, + "require": { + "christian-riesen/base32": "~1.3", + "paragonie/random_compat": "~1.4|~2.0", + "php": ">=5.4", + "symfony/polyfill-php56": "~1.2" + }, + "require-dev": { + "phpspec/phpspec": "~2.1" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes." + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ], + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2016-07-18 20:25:04" + }, + { + "name": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16 16:22:20" + }, + { + "name": "prologue/alerts", + "version": "0.4.0", + "source": { + "type": "git", + "url": "git@github.com:driesvints/Alerts.git", + "reference": "f4ea1e784070f43ddd12dab732552809e54ff7d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/driesvints/Alerts/zipball/f4ea1e784070f43ddd12dab732552809e54ff7d5", + "reference": "f4ea1e784070f43ddd12dab732552809e54ff7d5", + "shasum": "" + }, + "require": { + "illuminate/config": "~5", + "illuminate/session": "~5", + "illuminate/support": "~5", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Prologue\\Alerts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dries Vints", + "email": "dries.vints@gmail.com", + "homepage": "http://driesvints.com", + "role": "Maintainer" + } + ], + "description": "Prologue Alerts is a package that handles global site messages.", + "keywords": [ + "alerts", + "laravel", + "messages" + ], + "time": "2015-04-13 17:27:18" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "psy/psysh", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1", + "jakub-onderka/php-console-highlighter": "0.3.*", + "nikic/php-parser": "^1.2.1|~2.0", + "php": ">=5.3.9", + "symfony/console": "~2.3.10|^2.4.2|~3.0", + "symfony/var-dumper": "~2.7|~3.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "~1.5", + "phpunit/phpunit": "~3.7|~4.0|~5.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/finder": "~2.1|~3.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Psy/functions.php" + ], + "psr-4": { + "Psy\\": "src/Psy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "time": "2016-03-09 05:03:14" + }, + { + "name": "ramsey/uuid", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "5677cfe02397dd6b58c861870dfaa5d9007d3954" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/5677cfe02397dd6b58c861870dfaa5d9007d3954", + "reference": "5677cfe02397dd6b58c861870dfaa5d9007d3954", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0", + "php": ">=5.4" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "apigen/apigen": "^4.1", + "codeception/aspect-mock": "1.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.4", + "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", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2016-11-22 19:21:44" + }, + { + "name": "s1lentium/iptools", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/S1lentium/IPTools.git", + "reference": "cb4843d4077872643b5d38d18b8591b4aaf605ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/cb4843d4077872643b5d38d18b8591b4aaf605ea", + "reference": "cb4843d4077872643b5d38d18b8591b4aaf605ea", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "satooshi/php-coveralls": "~1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "IPTools\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Safarov Alisher", + "email": "alisher.safarov@outlook.com", + "homepage": "https://github.com/S1lentium" + } + ], + "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)", + "keywords": [ + "IP", + "IP-Tools", + "cidr", + "ipv4", + "ipv6", + "network", + "subnet" + ], + "time": "2016-08-21 15:57:09" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.5", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "cd142238a339459b10da3d8234220963f392540c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/cd142238a339459b10da3d8234220963f392540c", + "reference": "cd142238a339459b10da3d8234220963f392540c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2016-12-29 10:02:40" + }, + { + "name": "symfony/console", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "047f16485d68c083bd5d9b73ff16f9cb9c1a9f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/047f16485d68c083bd5d9b73ff16f9cb9c1a9f52", + "reference": "047f16485d68c083bd5d9b73ff16f9cb9c1a9f52", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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 Console Component", + "homepage": "https://symfony.com", + "time": "2017-01-08 20:43:43" + }, + { + "name": "symfony/debug", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "c6661361626b3cf5cf2089df98b3b5006a197e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/c6661361626b3cf5cf2089df98b3b5006a197e85", + "reference": "c6661361626b3cf5cf2089df98b3b5006a197e85", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "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/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-01-28 00:04:57" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9137eb3a3328e413212826d63eeeb0217836e2b6", + "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:32:22" + }, + { + "name": "symfony/finder", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "59687a255d1562f2c17b012418273862083d85f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/59687a255d1562f2c17b012418273862083d85f7", + "reference": "59687a255d1562f2c17b012418273862083d85f7", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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 Finder Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:31:54" + }, + { + "name": "symfony/http-foundation", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "cef0ad49a2e90455cfc649522025b5a2929648c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cef0ad49a2e90455cfc649522025b5a2929648c0", + "reference": "cef0ad49a2e90455cfc649522025b5a2929648c0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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 HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2017-01-08 20:43:43" + }, + { + "name": "symfony/http-kernel", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "c830387dec1b48c100473d10a6a356c3c3ae2a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c830387dec1b48c100473d10a6a356c3c3ae2a13", + "reference": "c830387dec1b48c100473d10a6a356c3c3ae2a13", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~2.8.13|~3.1.6|~3.2" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "symfony/browser-kit": "~2.8|~3.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": "~2.8|~3.0", + "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": "~2.8|~3.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-01-28 02:53:17" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "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 for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "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 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/process", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "2605753c5f8c531623d24d002825ebb1d6a22248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/2605753c5f8c531623d24d002825ebb1d6a22248", + "reference": "2605753c5f8c531623d24d002825ebb1d6a22248", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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 Process Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:13:55" + }, + { + "name": "symfony/routing", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "f25581d4eb0a82962c291917f826166f0dcd8a9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/f25581d4eb0a82962c291917f826166f0dcd8a9a", + "reference": "f25581d4eb0a82962c291917f826166f0dcd8a9a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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 Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2017-01-28 00:04:57" + }, + { + "name": "symfony/translation", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d5a20fab5f63f44c233c69b3041c3cb1d4945e45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d5a20fab5f63f44c233c69b3041c3cb1d4945e45", + "reference": "d5a20fab5f63f44c233c69b3041c3cb1d4945e45", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 Translation Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:01:39" + }, + { + "name": "symfony/var-dumper", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "16df11647e5b992d687cb4eeeb9a882d5f5c26b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/16df11647e5b992d687cb4eeeb9a882d5f5c26b9", + "reference": "16df11647e5b992d687cb4eeeb9a882d5f5c26b9", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "twig/twig": "~1.20|~2.0" + }, + "suggest": { + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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 mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2017-01-24 13:02:38" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Attribution" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2016-09-01 10:05:43" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23 20:04:58" + }, + { + "name": "webpatser/laravel-uuid", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/webpatser/laravel-uuid.git", + "reference": "6ed2705775e3edf066b90e1292f76f157ec00507" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webpatser/laravel-uuid/zipball/6ed2705775e3edf066b90e1292f76f157ec00507", + "reference": "6ed2705775e3edf066b90e1292f76f157ec00507", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "fzaninotto/faker": "1.5.*", + "phpunit/phpunit": "4.7.*" + }, + "suggest": { + "paragonie/random_compat": "A random_bytes Php 5.x polyfill." + }, + "type": "library", + "autoload": { + "psr-0": { + "Webpatser\\Uuid": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christoph Kempen", + "email": "christoph@downsized.nl" + } + ], + "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", + "keywords": [ + "UUID RFC4122" + ], + "time": "2016-05-09 09:22:18" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "fzaninotto/faker", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2016-04-29 12:21:54" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-05-11 14:41:42" + }, + { + "name": "laravel/homestead", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/homestead.git", + "reference": "705449c3dbedbded4bd4f3ed725303c69253cad4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/homestead/zipball/705449c3dbedbded4bd4f3ed725303c69253cad4", + "reference": "705449c3dbedbded4bd4f3ed725303c69253cad4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/console": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "bin": [ + "homestead" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Homestead\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "A virtual machine for web artisans.", + "time": "2016-02-16 22:31:00" + }, + { + "name": "mockery/mockery", + "version": "0.9.8", + "source": { + "type": "git", + "url": "https://github.com/padraic/mockery.git", + "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/padraic/mockery/zipball/1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", + "reference": "1e5e2ffdc4d71d7358ed58a6fdd30a4a0c506855", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "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", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2017-02-09 13:29:38" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "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", + "duplicate", + "object", + "object graph" + ], + "time": "2017-01-26 22:05:40" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0|^2.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-11-21 14:58:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c19cfc7cbb0e9338d8c469c7eedecc2a428b0971" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c19cfc7cbb0e9338d8c469c7eedecc2a428b0971", + "reference": "c19cfc7cbb0e9338d8c469c7eedecc2a428b0971", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-01-20 15:06:43" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03 07:40:28" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2016-11-15 14:06:22" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", + "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "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.2", + "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|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-02-10 09:05:10" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-12-08 20:27:08" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "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": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29 09:50:25" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26 07:53:53" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19 08:54:04" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-11-19 07:35:10" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19 07:33:16" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.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": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03 07:35:21" + }, + { + "name": "symfony/css-selector", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", + "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:31:54" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "7eede2a901a19928494194f7d1815a77b9a473a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7eede2a901a19928494194f7d1815a77b9a473a0", + "reference": "7eede2a901a19928494194f7d1815a77b9a473a0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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 DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:13:55" + }, + { + "name": "symfony/yaml", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e1718c6bf57e1efbb8793ada951584b2ab27775b", + "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:06:35" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "dingo/api": 10 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.4" + }, + "platform-dev": [] +} diff --git a/config/app.php b/config/app.php index bb517f5f7..622098a9b 100644 --- a/config/app.php +++ b/config/app.php @@ -6,6 +6,8 @@ return [ 'version' => env('APP_VERSION', 'canary'), + 'phrase_in_context' => env('PHRASE_IN_CONTEXT', false), + /* |-------------------------------------------------------------------------- | Application Debug Mode @@ -137,7 +139,6 @@ return [ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, @@ -149,6 +150,7 @@ return [ Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\EventServiceProvider::class, Pterodactyl\Providers\RouteServiceProvider::class, + Pterodactyl\Providers\PhraseAppTranslationProvider::class, /* * Additional Dependencies @@ -158,6 +160,7 @@ return [ 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, diff --git a/config/cache.php b/config/cache.php index 379135b0e..cb4bbd25d 100644 --- a/config/cache.php +++ b/config/cache.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => env('CACHE_DRIVER', 'file'), + 'default' => env('CACHE_DRIVER', 'memcached'), /* |-------------------------------------------------------------------------- @@ -51,7 +51,9 @@ return [ 'driver' => 'memcached', 'servers' => [ [ - 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100, + 'host' => env('MEMCACHE_DRIVER_HOST', '127.0.0.1'), + 'port' => env('MEMCACHE_DRIVER_PORT', 11211), + 'weight' => 100, ], ], ], diff --git a/config/database.php b/config/database.php index f6cf86b4c..420cd6184 100644 --- a/config/database.php +++ b/config/database.php @@ -55,6 +55,7 @@ return [ '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', ''), diff --git a/config/debugbar.php b/config/debugbar.php index 4c1349b40..4d9d8c45e 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -92,7 +92,7 @@ return [ 'views' => true, // Views with their data 'route' => true, // Current route information 'laravel' => false, // Laravel version and environment - 'events' => false, // All events fired + '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 @@ -119,11 +119,11 @@ return [ ], 'db' => [ 'with_params' => true, // Render SQL with the parameters substituted - 'timeline' => false, // Add the queries to the timeline - 'backtrace' => false, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. + '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'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ + 'types' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ ], 'hints' => true, // Show hints for common mistakes ], diff --git a/config/mail.php b/config/mail.php index 07ea9c147..6b07f59ff 100644 --- a/config/mail.php +++ b/config/mail.php @@ -54,7 +54,10 @@ return [ | */ - 'from' => ['address' => env('MAIL_FROM'), 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel')], + 'from' => [ + 'address' => env('MAIL_FROM'), + 'name' => env('MAIL_FROM_NAME', 'Pterodactyl Panel'), + ], /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index 59ad9182f..f1b004214 100644 --- a/config/session.php +++ b/config/session.php @@ -29,9 +29,9 @@ return [ | */ - 'lifetime' => 30, + 'lifetime' => 120, - 'expire_on_close' => true, + 'expire_on_close' => false, /* |-------------------------------------------------------------------------- diff --git a/config/trustedproxy.php b/config/trustedproxy.php new file mode 100644 index 000000000..a06211497 --- /dev/null +++ b/config/trustedproxy.php @@ -0,0 +1,60 @@ +getClientIp() + * always gets the originating client IP, no matter + * how many proxies that client's request has + * subsequently passed through. + */ + 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + + /* + * Or, to trust all proxies that connect + * directly to your server, uncomment this: + */ + // 'proxies' => '*', + + /* + * Or, to trust ALL proxies, including those that + * are in a chain of fowarding, uncomment this: + */ + // 'proxies' => '**', + + /* + * Default Header Names + * + * Change these if the proxy does + * not send the default header names. + * + * Note that headers such as X-Forwarded-For + * are transformed to HTTP_X_FORWARDED_FOR format. + * + * The following are Symfony defaults, found in + * \Symfony\Component\HttpFoundation\Request::$trustedHeaders + */ + 'headers' => [ + \Illuminate\Http\Request::HEADER_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', + ], +]; 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 87a66b40a..7cb3eb10e 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -17,12 +17,6 @@ class AddPackSupport extends Migration $table->increments('id'); $table->unsignedInteger('option'); $table->char('uuid', 36)->unique(); - $table->unsignedInteger('build_memory')->nullable(); - $table->unsignedInteger('build_swap')->nullable(); - $table->unsignedInteger('build_cpu')->nullable(); - $table->unsignedInteger('build_io')->nullable(); - $table->text('build_script')->nullable(); - $table->string('build_container')->default('alpine:latest'); $table->string('name'); $table->string('version'); $table->text('description')->nullable(); diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php new file mode 100644 index 000000000..800762417 --- /dev/null +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -0,0 +1,82 @@ +dropForeign('servers_node_foreign'); + $table->dropForeign('servers_owner_foreign'); + $table->dropForeign('servers_allocation_foreign'); + $table->dropForeign('servers_service_foreign'); + $table->dropForeign('servers_option_foreign'); + $table->dropForeign('servers_pack_foreign'); + + $table->dropIndex('servers_node_foreign'); + $table->dropIndex('servers_owner_foreign'); + $table->dropIndex('servers_allocation_foreign'); + $table->dropIndex('servers_service_foreign'); + $table->dropIndex('servers_option_foreign'); + $table->dropIndex('servers_pack_foreign'); + + $table->renameColumn('node', 'node_id'); + $table->renameColumn('owner', 'owner_id'); + $table->renameColumn('allocation', 'allocation_id'); + $table->renameColumn('service', 'service_id'); + $table->renameColumn('option', 'option_id'); + $table->renameColumn('pack', 'pack_id'); + + $table->foreign('node_id')->references('id')->on('nodes'); + $table->foreign('owner_id')->references('id')->on('users'); + $table->foreign('allocation_id')->references('id')->on('allocations'); + $table->foreign('service_id')->references('id')->on('services'); + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign('servers_node_id_foreign'); + $table->dropForeign('servers_owner_id_foreign'); + $table->dropForeign('servers_allocation_id_foreign'); + $table->dropForeign('servers_service_id_foreign'); + $table->dropForeign('servers_option_id_foreign'); + $table->dropForeign('servers_pack_id_foreign'); + + $table->dropIndex('servers_node_id_foreign'); + $table->dropIndex('servers_owner_id_foreign'); + $table->dropIndex('servers_allocation_id_foreign'); + $table->dropIndex('servers_service_id_foreign'); + $table->dropIndex('servers_option_id_foreign'); + $table->dropIndex('servers_pack_id_foreign'); + + $table->renameColumn('node_id', 'node'); + $table->renameColumn('owner_id', 'owner'); + $table->renameColumn('allocation_id', 'allocation'); + $table->renameColumn('service_id', 'service'); + $table->renameColumn('option_id', 'option'); + $table->renameColumn('pack_id', 'pack'); + + $table->foreign('node')->references('id')->on('nodes'); + $table->foreign('owner')->references('id')->on('users'); + $table->foreign('allocation')->references('id')->on('allocations'); + $table->foreign('service')->references('id')->on('services'); + $table->foreign('option')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php new file mode 100644 index 000000000..4408e612b --- /dev/null +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -0,0 +1,40 @@ +dropForeign('nodes_location_foreign'); + $table->dropIndex('nodes_location_foreign'); + + $table->renameColumn('location', 'location_id'); + $table->foreign('location_id')->references('id')->on('locations'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropForeign('nodes_location_id_foreign'); + $table->dropIndex('nodes_location_id_foreign'); + + $table->renameColumn('location_id', 'location'); + $table->foreign('location')->references('id')->on('locations'); + }); + } +} diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php new file mode 100644 index 000000000..519ccc826 --- /dev/null +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -0,0 +1,48 @@ +dropForeign('allocations_node_foreign'); + $table->dropForeign('allocations_assigned_to_foreign'); + $table->dropIndex('allocations_node_foreign'); + $table->dropIndex('allocations_assigned_to_foreign'); + + $table->renameColumn('node', 'node_id'); + $table->renameColumn('assigned_to', 'server_id'); + $table->foreign('node_id')->references('id')->on('nodes'); + $table->foreign('server_id')->references('id')->on('servers'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign('allocations_node_id_foreign'); + $table->dropForeign('allocations_server_id_foreign'); + $table->dropIndex('allocations_node_id_foreign'); + $table->dropIndex('allocations_server_id_foreign'); + + $table->renameColumn('node_id', 'node'); + $table->renameColumn('server_id', 'assigned_to'); + $table->foreign('node')->references('id')->on('nodes'); + $table->foreign('assigned_to')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php new file mode 100644 index 000000000..ddb37b891 --- /dev/null +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -0,0 +1,40 @@ +dropForeign('service_options_parent_service_foreign'); + $table->dropIndex('service_options_parent_service_foreign'); + + $table->renameColumn('parent_service', 'service_id'); + $table->foreign('service_id')->references('id')->on('services'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign('service_options_service_id_foreign'); + $table->dropIndex('service_options_service_id_foreign'); + + $table->renameColumn('service_id', 'parent_service'); + $table->foreign('parent_service')->references('id')->on('services'); + }); + } +} diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php new file mode 100644 index 000000000..5e57ffef3 --- /dev/null +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -0,0 +1,40 @@ +dropForeign('service_packs_option_foreign'); + $table->dropIndex('service_packs_option_foreign'); + + $table->renameColumn('option', 'option_id'); + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_packs', function (Blueprint $table) { + $table->dropForeign('service_packs_option_id_foreign'); + $table->dropIndex('service_packs_option_id_foreign'); + + $table->renameColumn('option_id', 'option'); + $table->foreign('option')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php new file mode 100644 index 000000000..7194bf075 --- /dev/null +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -0,0 +1,75 @@ +unsignedInteger('subuser_id')->after('id'); + }); + + DB::transaction(function () { + foreach (Subuser::all() as &$subuser) { + Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ + 'subuser_id' => $subuser->id, + ]); + } + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_server_id_foreign'); + $table->dropIndex('permissions_server_id_foreign'); + $table->dropForeign('permissions_user_id_foreign'); + $table->dropIndex('permissions_user_id_foreign'); + + $table->dropColumn('server_id'); + $table->dropColumn('user_id'); + $table->dropColumn('created_at'); + $table->dropColumn('updated_at'); + $table->foreign('subuser_id')->references('id')->on('subusers'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('permissions', function (Blueprint $table) { + $table->unsignedInteger('server_id')->after('subuser_id'); + $table->unsignedInteger('user_id')->after('server_id'); + $table->timestamps(); + }); + + DB::transaction(function () { + foreach (Subuser::all() as &$subuser) { + Permission::where('subuser_id', $subuser->id)->update([ + 'user_id' => $subuser->user_id, + 'server_id' => $subuser->server_id, + ]); + } + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_subuser_id_foreign'); + $table->dropIndex('permissions_subuser_id_foreign'); + $table->dropColumn('subuser_id'); + + $table->foreign('server_id')->references('id')->on('servers'); + $table->foreign('user_id')->references('id')->on('users'); + }); + } +} diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php new file mode 100644 index 000000000..358f9938d --- /dev/null +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -0,0 +1,38 @@ +dropForeign('api_keys_user_foreign')->dropIndex('api_keys_user_foreign'); + + $table->renameColumn('user', 'user_id'); + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropForeign('api_keys_user_id_foreign')->dropIndex('api_keys_user_id_foreign'); + + $table->renameColumn('user_id', 'user'); + $table->foreign('user')->references('id')->on('users'); + }); + } +} diff --git a/database/seeds/MinecraftServiceTableSeeder.php b/database/seeds/MinecraftServiceTableSeeder.php index 28f61343e..45f251ebd 100644 --- a/database/seeds/MinecraftServiceTableSeeder.php +++ b/database/seeds/MinecraftServiceTableSeeder.php @@ -66,8 +66,8 @@ class MinecraftServiceTableSeeder extends Seeder private function addCoreOptions() { - $this->option['vanilla'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['vanilla'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'vanilla', @@ -76,8 +76,8 @@ class MinecraftServiceTableSeeder extends Seeder 'startup' => null, ]); - $this->option['spigot'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['spigot'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'spigot', @@ -86,8 +86,8 @@ class MinecraftServiceTableSeeder extends Seeder 'startup' => '-Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', ]); - $this->option['sponge'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['sponge'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, 'name' => 'Sponge (SpongeVanilla)', 'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.', 'tag' => 'sponge', @@ -96,8 +96,8 @@ class MinecraftServiceTableSeeder extends Seeder 'startup' => null, ]); - $this->option['bungeecord'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['bungeecord'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'bungeecord', @@ -117,7 +117,7 @@ class MinecraftServiceTableSeeder extends Seeder private function addVanillaVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['vanilla']->id, 'name' => 'Server Jar File', 'description' => 'The name of the server jarfile to run the server with.', @@ -129,7 +129,7 @@ class MinecraftServiceTableSeeder extends Seeder 'regex' => '/^([\w\d._-]+)(\.jar)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['vanilla']->id, 'name' => 'Server Version', 'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.', @@ -144,7 +144,7 @@ class MinecraftServiceTableSeeder extends Seeder private function addSpigotVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['spigot']->id, 'name' => 'Server Jar File', 'description' => 'The name of the server jarfile to run the server with.', @@ -156,7 +156,7 @@ class MinecraftServiceTableSeeder extends Seeder 'regex' => '/^([\w\d._-]+)(\.jar)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['spigot']->id, 'name' => 'Spigot Version', 'description' => 'The version of Spigot to download (using the --rev tag). Use "latest" for latest.', @@ -168,7 +168,7 @@ class MinecraftServiceTableSeeder extends Seeder 'regex' => '/^(latest|[a-zA-Z0-9_\.-]{3,7})$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['spigot']->id, '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).', @@ -183,7 +183,7 @@ class MinecraftServiceTableSeeder extends Seeder private function addSpongeVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['sponge']->id, 'name' => 'Sponge Version', 'description' => 'The version of SpongeVanilla to download and use.', @@ -195,7 +195,7 @@ class MinecraftServiceTableSeeder extends Seeder 'regex' => '/^([a-zA-Z0-9.\-_]+)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['sponge']->id, 'name' => 'Server Jar File', 'description' => 'The name of the Jarfile to use when running SpongeVanilla.', @@ -210,7 +210,7 @@ class MinecraftServiceTableSeeder extends Seeder private function addBungeecordVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['bungeecord']->id, 'name' => 'Bungeecord Version', 'description' => 'The version of Bungeecord to download and use.', @@ -222,7 +222,7 @@ class MinecraftServiceTableSeeder extends Seeder 'regex' => '/^(latest|[\d]{1,6})$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['bungeecord']->id, 'name' => 'Bungeecord Jar File', 'description' => 'The name of the Jarfile to use when running Bungeecord.', diff --git a/database/seeds/SourceServiceTableSeeder.php b/database/seeds/SourceServiceTableSeeder.php index 838814db8..87623335c 100644 --- a/database/seeds/SourceServiceTableSeeder.php +++ b/database/seeds/SourceServiceTableSeeder.php @@ -66,8 +66,8 @@ class SourceServiceTableSeeder extends Seeder private function addCoreOptions() { - $this->option['insurgency'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['insurgency'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'srcds', @@ -76,8 +76,8 @@ class SourceServiceTableSeeder extends Seeder 'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart', ]); - $this->option['tf2'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['tf2'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'srcds', @@ -86,8 +86,8 @@ class SourceServiceTableSeeder extends Seeder 'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart', ]); - $this->option['ark'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['ark'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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', 'tag' => 'ark', @@ -96,8 +96,8 @@ class SourceServiceTableSeeder extends Seeder 'startup' => 'TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}', ]); - $this->option['custom'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['custom'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'srcds', @@ -117,7 +117,7 @@ class SourceServiceTableSeeder extends Seeder private function addInsurgencyVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['insurgency']->id, 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', @@ -129,7 +129,7 @@ class SourceServiceTableSeeder extends Seeder 'regex' => '/^(17705)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['insurgency']->id, 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', @@ -141,7 +141,7 @@ class SourceServiceTableSeeder extends Seeder 'regex' => '/^(insurgency)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['insurgency']->id, 'name' => 'Default Map', 'description' => 'The default map to use when starting the server.', @@ -156,7 +156,7 @@ class SourceServiceTableSeeder extends Seeder private function addTF2Variables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['tf2']->id, 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', @@ -168,7 +168,7 @@ class SourceServiceTableSeeder extends Seeder 'regex' => '/^(232250)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['tf2']->id, 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', @@ -180,7 +180,7 @@ class SourceServiceTableSeeder extends Seeder 'regex' => '/^(tf)$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['tf2']->id, 'name' => 'Default Map', 'description' => 'The default map to use when starting the server.', @@ -234,7 +234,7 @@ class SourceServiceTableSeeder extends Seeder private function addCustomVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['custom']->id, 'name' => 'Game ID', 'description' => 'The ID corresponding to the game to download and run using SRCDS.', @@ -246,7 +246,7 @@ class SourceServiceTableSeeder extends Seeder 'regex' => '/^(\d){1,6}$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['custom']->id, 'name' => 'Game Name', 'description' => 'The name corresponding to the game to download and run using SRCDS.', diff --git a/database/seeds/TerrariaServiceTableSeeder.php b/database/seeds/TerrariaServiceTableSeeder.php index bd3089ea2..59fd1e519 100644 --- a/database/seeds/TerrariaServiceTableSeeder.php +++ b/database/seeds/TerrariaServiceTableSeeder.php @@ -66,8 +66,8 @@ class TerrariaServiceTableSeeder extends Seeder private function addCoreOptions() { - $this->option['tshock'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['tshock'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'tshock', @@ -79,7 +79,7 @@ class TerrariaServiceTableSeeder extends Seeder private function addVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['tshock']->id, 'name' => 'TShock Version', 'description' => 'Which version of TShock to install and use.', @@ -91,7 +91,7 @@ class TerrariaServiceTableSeeder extends Seeder 'regex' => '/^([0-9_\.-]{5,10})$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['tshock']->id, 'name' => 'Maximum Slots', 'description' => 'Total number of slots to allow on the server.', diff --git a/database/seeds/VoiceServiceTableSeeder.php b/database/seeds/VoiceServiceTableSeeder.php index 1fca3a475..3856ac76a 100644 --- a/database/seeds/VoiceServiceTableSeeder.php +++ b/database/seeds/VoiceServiceTableSeeder.php @@ -66,8 +66,8 @@ class VoiceServiceTableSeeder extends Seeder private function addCoreOptions() { - $this->option['mumble'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['mumble'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, 'name' => 'Mumble Server', 'description' => 'Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.', 'tag' => 'mumble', @@ -76,8 +76,8 @@ class VoiceServiceTableSeeder extends Seeder 'startup' => '-fg', ]); - $this->option['ts3'] = Models\ServiceOptions::create([ - 'parent_service' => $this->service->id, + $this->option['ts3'] = Models\ServiceOption::create([ + 'service_id' => $this->service->id, '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.', 'tag' => 'ts3', @@ -89,7 +89,7 @@ class VoiceServiceTableSeeder extends Seeder private function addVariables() { - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['mumble']->id, 'name' => 'Maximum Users', 'description' => 'Maximum concurrent users on the mumble server.', @@ -101,7 +101,7 @@ class VoiceServiceTableSeeder extends Seeder 'regex' => '/^(\d){1,6}$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['mumble']->id, 'name' => 'Server Version', 'description' => 'Version of Mumble Server to download and use.', @@ -113,7 +113,7 @@ class VoiceServiceTableSeeder extends Seeder 'regex' => '/^([0-9_\.-]{5,8})$/', ]); - Models\ServiceVariables::create([ + Models\ServiceVariable::create([ 'option_id' => $this->option['ts3']->id, 'name' => 'Server Version', 'description' => 'The version of Teamspeak 3 to use when running the server.', diff --git a/public/.htaccess b/public/.htaccess index 8eb2dd0dd..342448645 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -8,6 +8,10 @@ # 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] # Handle Front Controller... RewriteCond %{REQUEST_FILENAME} !-d diff --git a/public/js/laroute.js b/public/js/laroute.js index f9a87a665..633b139f9 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -6,7 +6,7 @@ absolute: false, rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"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@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@getJson"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{id}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@updateUser"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@deleteUser"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-nodes","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetNodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-ips","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetIps"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/service-options","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerServiceOptions"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/option-details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerOptionDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/database","name":"admin.servers.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/container","name":"admin.servers.post.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateContainerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/startup","name":"admin.servers.post.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/rebuild","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerToggleBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerUpdateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/suspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postSuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/unsuspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUnsuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/installed","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postToggleInstall"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{id}\/{force?}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/queuedDeletion","name":"admin.servers.post.queuedDeletion","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postQueuedDeletionHandler"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postView"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/single\/{allocation}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateSingle"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/block","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/alias","name":"admin.nodes.alias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setAlias"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/allocations.json","name":"admin.nodes.view.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getAllocationsJson"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocations","name":"admin.nodes.post.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@postAllocations"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/deploy","name":"admin.nodes.deply","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getScript"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@deleteNode"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/{id}\/configurationtoken","name":"admin.nodes.configuration-token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getConfigurationToken"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationsController@getIndex"},{"host":null,"methods":["DELETE"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@deleteLocation"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@patchLocation"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@postLocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/new","name":"admin.databases.new","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/databases\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@postNew"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete\/{id}","name":"admin.databases.delete","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteDatabase"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete-server\/{id}","name":"admin.databases.delete-server","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteServer"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}","name":"admin.services.service","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getService"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postService"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}\/configuration","name":"admin.services.service.config","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getConfiguration"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}\/configuration","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@newOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":"admin.services.option","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOption"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{service}\/option\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":"admin.services.option.variable.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}","name":"admin.services.option.variable","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOptionVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}\/delete","name":"admin.services.option.variable.delete","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/new\/{option?}","name":"admin.services.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@new"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/upload\/{option?}","name":"admin.services.packs.uploadForm","action":"Pterodactyl\Http\Controllers\Admin\PackController@uploadForm"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/upload","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@postUpload"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs","name":"admin.services.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@listAll"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/option\/{option}","name":"admin.services.packs.option","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/service\/{service}","name":"admin.services.packs.service","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}","name":"admin.services.packs.edit","action":"Pterodactyl\Http\Controllers\Admin\PackController@edit"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/edit\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}\/export\/{archive?}","name":"admin.services.packs.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"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@checkTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"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\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"index","name":null,"action":"Closure"},{"host":null,"methods":["GET","HEAD"],"uri":"password-gen\/{length}","name":"password-gen","action":"Pterodactyl\Http\Controllers\Base\IndexController@getPassword"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account\/password","name":"account.password","action":"Pterodactyl\Http\Controllers\Base\AccountController@password"},{"host":null,"methods":["POST"],"uri":"account\/email","name":"account.email","action":"Pterodactyl\Http\Controllers\Base\AccountController@email"},{"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@save"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":null,"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":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@list"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"remote.install","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"language\/{lang}","name":"langauge.set","action":"Pterodactyl\Http\Controllers\Base\LanguageController@setLanguage"},{"host":null,"methods":["POST"],"uri":"remote\/download","name":"remote.download","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postDownload"},{"host":null,"methods":["POST"],"uri":"remote\/install","name":"remote.install","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postInstall"},{"host":null,"methods":["POST"],"uri":"remote\/event","name":"remote.event","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@event"},{"host":null,"methods":["GET","HEAD"],"uri":"remote\/configuration\/{token}","name":"remote.configuration","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@getConfiguration"},{"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}\/settings","name":"server.settings","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSettings"},{"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":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"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}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"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\/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.download","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDownloadFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAddFile"},{"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@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{id}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getView"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postView"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/delete\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@deleteSubuser"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks","name":"server.tasks","action":"Pterodactyl\Http\Controllers\Server\TaskController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/view\/{id}","name":"server.tasks.view","action":"Pterodactyl\Http\Controllers\Server\TaskController@getView"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/new","name":"server.tasks.new","action":"Pterodactyl\Http\Controllers\Server\TaskController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\TaskController@postNew"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/tasks\/delete\/{id}","name":"server.tasks.delete","action":"Pterodactyl\Http\Controllers\Server\TaskController@deleteTask"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/toggle\/{id}","name":"server.tasks.toggle","action":"Pterodactyl\Http\Controllers\Server\TaskController@toggleTask"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/ajax\/status","name":"server.ajax.status","action":"Pterodactyl\Http\Controllers\Server\AjaxController@getStatus"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/set-primary","name":null,"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":"_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"}], + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"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@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@getJson"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{id}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@updateUser"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@deleteUser"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-nodes","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetNodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-ips","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetIps"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/service-options","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerServiceOption"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/option-details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerOptionDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/database","name":"admin.servers.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/container","name":"admin.servers.post.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateContainerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/startup","name":"admin.servers.post.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/rebuild","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerToggleBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerUpdateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/suspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postSuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/unsuspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUnsuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/installed","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postToggleInstall"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{id}\/{force?}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/queuedDeletion","name":"admin.servers.post.queuedDeletion","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postQueuedDeletionHandler"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postView"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/single\/{allocation}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateSingle"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/block","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/alias","name":"admin.nodes.alias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setAlias"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/allocations.json","name":"admin.nodes.view.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getAllocationsJson"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocations","name":"admin.nodes.post.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@postAllocations"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/deploy","name":"admin.nodes.deply","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getScript"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@deleteNode"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/{id}\/configurationtoken","name":"admin.nodes.configuration-token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getConfigurationToken"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationsController@getIndex"},{"host":null,"methods":["DELETE"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@deleteLocation"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@patchLocation"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@postLocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/new","name":"admin.databases.new","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/databases\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@postNew"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete\/{id}","name":"admin.databases.delete","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteDatabase"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete-server\/{id}","name":"admin.databases.delete-server","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteServer"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}","name":"admin.services.service","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getService"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postService"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}\/configuration","name":"admin.services.service.config","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getConfiguration"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}\/configuration","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@newOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":"admin.services.option","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOption"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{service}\/option\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":"admin.services.option.variable.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}","name":"admin.services.option.variable","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOptionVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}\/delete","name":"admin.services.option.variable.delete","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/new\/{option?}","name":"admin.services.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@new"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/upload\/{option?}","name":"admin.services.packs.uploadForm","action":"Pterodactyl\Http\Controllers\Admin\PackController@uploadForm"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/upload","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@postUpload"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs","name":"admin.services.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@listAll"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/option\/{option}","name":"admin.services.packs.option","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/service\/{service}","name":"admin.services.packs.service","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}","name":"admin.services.packs.edit","action":"Pterodactyl\Http\Controllers\Admin\PackController@edit"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/edit\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}\/export\/{archive?}","name":"admin.services.packs.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"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\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"index","name":null,"action":"Closure"},{"host":null,"methods":["GET","HEAD"],"uri":"password-gen\/{length}","name":"password-gen","action":"Pterodactyl\Http\Controllers\Base\IndexController@getPassword"},{"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@save"},{"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":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@list"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"remote.install","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":"language\/{lang}","name":"langauge.set","action":"Pterodactyl\Http\Controllers\Base\LanguageController@setLanguage"},{"host":null,"methods":["POST"],"uri":"remote\/download","name":"remote.download","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postDownload"},{"host":null,"methods":["POST"],"uri":"remote\/install","name":"remote.install","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"remote\/configuration\/{token}","name":"remote.configuration","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@getConfiguration"},{"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}\/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":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"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}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"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\/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.download","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDownloadFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAddFile"},{"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@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{id}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getView"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postView"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/delete\/{id}","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@deleteSubuser"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks","name":"server.tasks","action":"Pterodactyl\Http\Controllers\Server\TaskController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/view\/{id}","name":"server.tasks.view","action":"Pterodactyl\Http\Controllers\Server\TaskController@getView"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/new","name":"server.tasks.new","action":"Pterodactyl\Http\Controllers\Server\TaskController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\TaskController@postNew"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/tasks\/delete\/{id}","name":"server.tasks.delete","action":"Pterodactyl\Http\Controllers\Server\TaskController@deleteTask"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/toggle\/{id}","name":"server.tasks.toggle","action":"Pterodactyl\Http\Controllers\Server\TaskController@toggleTask"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/ajax\/status","name":"server.ajax.status","action":"Pterodactyl\Http\Controllers\Server\AjaxController@getStatus"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/set-primary","name":null,"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":"_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"}], prefix: '', route : function (name, parameters, route) { diff --git a/public/js/phraseapp.js b/public/js/phraseapp.js new file mode 100644 index 000000000..a11a22655 --- /dev/null +++ b/public/js/phraseapp.js @@ -0,0 +1,8 @@ +window.PHRASEAPP_CONFIG = { + projectId: '94f8b39450cd749ae9c3cc0ab8cdb61d' +}; +(function() { + var phraseapp = document.createElement('script'); phraseapp.type = 'text/javascript'; phraseapp.async = true; + phraseapp.src = ['https://', 'phraseapp.com/assets/in-context-editor/2.0/app.js?', new Date().getTime()].join(''); + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(phraseapp, s); +})(); diff --git a/public/js/vendor/ace/mode-vbscript.js b/public/js/vendor/ace/mode-vbscript.js index 1de7b7c48..da888780f 100755 --- a/public/js/vendor/ace/mode-vbscript.js +++ b/public/js/vendor/ace/mode-vbscript.js @@ -1 +1 @@ -define("ace/mode/vbscript_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(){var e=this.createKeywordMapper({"keyword.control.asp":"If|Then|Else|ElseIf|End|While|Wend|For|To|Each|Case|Select|Return|Continue|Do|Until|Loop|Next|With|Exit|Function|Property|Type|Enum|Sub|IIf","storage.type.asp":"Dim|Call|Class|Const|Dim|Redim|Set|Let|Get|New|Randomize|Option|Explicit","storage.modifier.asp":"Private|Public|Default","keyword.operator.asp":"Mod|And|Not|Or|Xor|as","constant.language.asp":"Empty|False|Nothing|Null|True","support.class.asp":"Application|ObjectContext|Request|Response|Server|Session","support.class.collection.asp":"Contents|StaticObjects|ClientCertificate|Cookies|Form|QueryString|ServerVariables","support.constant.asp":"TotalBytes|Buffer|CacheControl|Charset|ContentType|Expires|ExpiresAbsolute|IsClientConnected|PICS|Status|ScriptTimeout|CodePage|LCID|SessionID|Timeout","support.function.asp":"Lock|Unlock|SetAbort|SetComplete|BinaryRead|AddHeader|AppendToLog|BinaryWrite|Clear|Flush|Redirect|Write|CreateObject|HTMLEncode|MapPath|URLEncode|Abandon|Convert|Regex","support.function.event.asp":"Application_OnEnd|Application_OnStart|OnTransactionAbort|OnTransactionCommit|Session_OnEnd|Session_OnStart","support.function.vb.asp":"Array|Add|Asc|Atn|CBool|CByte|CCur|CDate|CDbl|Chr|CInt|CLng|Conversions|Cos|CreateObject|CSng|CStr|Date|DateAdd|DateDiff|DatePart|DateSerial|DateValue|Day|Derived|Math|Escape|Eval|Exists|Exp|Filter|FormatCurrency|FormatDateTime|FormatNumber|FormatPercent|GetLocale|GetObject|GetRef|Hex|Hour|InputBox|InStr|InStrRev|Int|Fix|IsArray|IsDate|IsEmpty|IsNull|IsNumeric|IsObject|Item|Items|Join|Keys|LBound|LCase|Left|Len|LoadPicture|Log|LTrim|RTrim|Trim|Maths|Mid|Minute|Month|MonthName|MsgBox|Now|Oct|Remove|RemoveAll|Replace|RGB|Right|Rnd|Round|ScriptEngine|ScriptEngineBuildVersion|ScriptEngineMajorVersion|ScriptEngineMinorVersion|Second|SetLocale|Sgn|Sin|Space|Split|Sqr|StrComp|String|StrReverse|Tan|Time|Timer|TimeSerial|TimeValue|TypeName|UBound|UCase|Unescape|VarType|Weekday|WeekdayName|Year","support.type.vb.asp":"vbtrue|vbfalse|vbcr|vbcrlf|vbformfeed|vblf|vbnewline|vbnullchar|vbnullstring|int32|vbtab|vbverticaltab|vbbinarycompare|vbtextcomparevbsunday|vbmonday|vbtuesday|vbwednesday|vbthursday|vbfriday|vbsaturday|vbusesystemdayofweek|vbfirstjan1|vbfirstfourdays|vbfirstfullweek|vbgeneraldate|vblongdate|vbshortdate|vblongtime|vbshorttime|vbobjecterror|vbEmpty|vbNull|vbInteger|vbLong|vbSingle|vbDouble|vbCurrency|vbDate|vbString|vbObject|vbError|vbBoolean|vbVariant|vbDataObject|vbDecimal|vbByte|vbArray"},"identifier",!0);this.$rules={start:[{token:["meta.ending-space"],regex:"$"},{token:[null],regex:"^(?=\\t)",next:"state_3"},{token:[null],regex:"^(?= )",next:"state_4"},{token:["text","storage.type.function.asp","text","entity.name.function.asp","text","punctuation.definition.parameters.asp","variable.parameter.function.asp","punctuation.definition.parameters.asp"],regex:"^(\\s*)(Function|Sub)(\\s+)([a-zA-Z_]\\w*)(\\s*)(\\()([^)]*)(\\))"},{token:"punctuation.definition.comment.asp",regex:"'|REM(?=\\s|$)",next:"comment",caseInsensitive:!0},{token:"storage.type.asp",regex:"On Error Resume Next|On Error GoTo",caseInsensitive:!0},{token:"punctuation.definition.string.begin.asp",regex:'"',next:"string"},{token:["punctuation.definition.variable.asp"],regex:"(\\$)[a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*?\\b\\s*"},{token:"constant.numeric.asp",regex:"-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)(?:L|l|UL|ul|u|U|F|f)?\\b"},{regex:"\\w+",token:e},{token:["entity.name.function.asp"],regex:"(?:(\\b[a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*?\\b)(?=\\(\\)?))"},{token:["keyword.operator.asp"],regex:"\\-|\\+|\\*\\/|\\>|\\<|\\=|\\&"}],state_3:[{token:["meta.odd-tab.tabs","meta.even-tab.tabs"],regex:"(\\t)(\\t)?"},{token:"meta.leading-space",regex:"(?=[^\\t])",next:"start"},{token:"meta.leading-space",regex:".",next:"state_3"}],state_4:[{token:["meta.odd-tab.spaces","meta.even-tab.spaces"],regex:"( )( )?"},{token:"meta.leading-space",regex:"(?=[^ ])",next:"start"},{defaultToken:"meta.leading-space"}],comment:[{token:"comment.line.apostrophe.asp",regex:"$|(?=(?:%>))",next:"start"},{defaultToken:"comment.line.apostrophe.asp"}],string:[{token:"constant.character.escape.apostrophe.asp",regex:'""'},{token:"string.quoted.double.asp",regex:'"',next:"start"},{defaultToken:"string.quoted.double.asp"}]}};r.inherits(s,i),t.VBScriptHighlightRules=s}),define("ace/mode/vbscript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/vbscript_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./vbscript_highlight_rules").VBScriptHighlightRules,o=function(){this.HighlightRules=s};r.inherits(o,i),function(){this.lineCommentStart=["'","REM"],this.$id="ace/mode/vbscript"}.call(o.prototype),t.Mode=o}) \ No newline at end of file +define("ace/mode/vbscript_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(){var e=this.createKeywordMapper({"keyword.control.asp":"If|Then|Else|ElseIf|End|While|Wend|For|To|Each|Case|Select|Return|Continue|Do|Until|Loop|Next|With|Exit|Function|Property|Type|Enum|Sub|IIf","storage.type.asp":"Dim|Call|Class|Const|Dim|Redim|Set|Let|Get|New|Randomize|Option|Explicit","storage.modifier.asp":"Private|Public|Default","keyword.operator.asp":"Mod|And|Not|Or|Xor|as","constant.language.asp":"Empty|False|Nothing|Null|True","support.class.asp":"Application|ObjectContext|Request|Response|Server|Session","support.class.collection.asp":"Contents|StaticObjects|ClientCertificate|Cookies|Form|QueryString|ServerVariable","support.constant.asp":"TotalBytes|Buffer|CacheControl|Charset|ContentType|Expires|ExpiresAbsolute|IsClientConnected|PICS|Status|ScriptTimeout|CodePage|LCID|SessionID|Timeout","support.function.asp":"Lock|Unlock|SetAbort|SetComplete|BinaryRead|AddHeader|AppendToLog|BinaryWrite|Clear|Flush|Redirect|Write|CreateObject|HTMLEncode|MapPath|URLEncode|Abandon|Convert|Regex","support.function.event.asp":"Application_OnEnd|Application_OnStart|OnTransactionAbort|OnTransactionCommit|Session_OnEnd|Session_OnStart","support.function.vb.asp":"Array|Add|Asc|Atn|CBool|CByte|CCur|CDate|CDbl|Chr|CInt|CLng|Conversions|Cos|CreateObject|CSng|CStr|Date|DateAdd|DateDiff|DatePart|DateSerial|DateValue|Day|Derived|Math|Escape|Eval|Exists|Exp|Filter|FormatCurrency|FormatDateTime|FormatNumber|FormatPercent|GetLocale|GetObject|GetRef|Hex|Hour|InputBox|InStr|InStrRev|Int|Fix|IsArray|IsDate|IsEmpty|IsNull|IsNumeric|IsObject|Item|Items|Join|Keys|LBound|LCase|Left|Len|LoadPicture|Log|LTrim|RTrim|Trim|Maths|Mid|Minute|Month|MonthName|MsgBox|Now|Oct|Remove|RemoveAll|Replace|RGB|Right|Rnd|Round|ScriptEngine|ScriptEngineBuildVersion|ScriptEngineMajorVersion|ScriptEngineMinorVersion|Second|SetLocale|Sgn|Sin|Space|Split|Sqr|StrComp|String|StrReverse|Tan|Time|Timer|TimeSerial|TimeValue|TypeName|UBound|UCase|Unescape|VarType|Weekday|WeekdayName|Year","support.type.vb.asp":"vbtrue|vbfalse|vbcr|vbcrlf|vbformfeed|vblf|vbnewline|vbnullchar|vbnullstring|int32|vbtab|vbverticaltab|vbbinarycompare|vbtextcomparevbsunday|vbmonday|vbtuesday|vbwednesday|vbthursday|vbfriday|vbsaturday|vbusesystemdayofweek|vbfirstjan1|vbfirstfourdays|vbfirstfullweek|vbgeneraldate|vblongdate|vbshortdate|vblongtime|vbshorttime|vbobjecterror|vbEmpty|vbNull|vbInteger|vbLong|vbSingle|vbDouble|vbCurrency|vbDate|vbString|vbObject|vbError|vbBoolean|vbVariant|vbDataObject|vbDecimal|vbByte|vbArray"},"identifier",!0);this.$rules={start:[{token:["meta.ending-space"],regex:"$"},{token:[null],regex:"^(?=\\t)",next:"state_3"},{token:[null],regex:"^(?= )",next:"state_4"},{token:["text","storage.type.function.asp","text","entity.name.function.asp","text","punctuation.definition.parameters.asp","variable.parameter.function.asp","punctuation.definition.parameters.asp"],regex:"^(\\s*)(Function|Sub)(\\s+)([a-zA-Z_]\\w*)(\\s*)(\\()([^)]*)(\\))"},{token:"punctuation.definition.comment.asp",regex:"'|REM(?=\\s|$)",next:"comment",caseInsensitive:!0},{token:"storage.type.asp",regex:"On Error Resume Next|On Error GoTo",caseInsensitive:!0},{token:"punctuation.definition.string.begin.asp",regex:'"',next:"string"},{token:["punctuation.definition.variable.asp"],regex:"(\\$)[a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*?\\b\\s*"},{token:"constant.numeric.asp",regex:"-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)(?:L|l|UL|ul|u|U|F|f)?\\b"},{regex:"\\w+",token:e},{token:["entity.name.function.asp"],regex:"(?:(\\b[a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*?\\b)(?=\\(\\)?))"},{token:["keyword.operator.asp"],regex:"\\-|\\+|\\*\\/|\\>|\\<|\\=|\\&"}],state_3:[{token:["meta.odd-tab.tabs","meta.even-tab.tabs"],regex:"(\\t)(\\t)?"},{token:"meta.leading-space",regex:"(?=[^\\t])",next:"start"},{token:"meta.leading-space",regex:".",next:"state_3"}],state_4:[{token:["meta.odd-tab.spaces","meta.even-tab.spaces"],regex:"( )( )?"},{token:"meta.leading-space",regex:"(?=[^ ])",next:"start"},{defaultToken:"meta.leading-space"}],comment:[{token:"comment.line.apostrophe.asp",regex:"$|(?=(?:%>))",next:"start"},{defaultToken:"comment.line.apostrophe.asp"}],string:[{token:"constant.character.escape.apostrophe.asp",regex:'""'},{token:"string.quoted.double.asp",regex:'"',next:"start"},{defaultToken:"string.quoted.double.asp"}]}};r.inherits(s,i),t.VBScriptHighlightRules=s}),define("ace/mode/vbscript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/vbscript_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./vbscript_highlight_rules").VBScriptHighlightRules,o=function(){this.HighlightRules=s};r.inherits(o,i),function(){this.lineCommentStart=["'","REM"],this.$id="ace/mode/vbscript"}.call(o.prototype),t.Mode=o}) \ No newline at end of file diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index 16dbe1547..e5fefe52f 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -20,10 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + .login-box, .register-box { + width: 40%; + margin: 7% auto + } -.login-box, .register-box { - width: 460px; -} + @media (max-width:768px) { + .login-box, .register-box { + width: 90%; + margin-top: 20px + } + } .weight-100 { font-weight: 100; diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js index e8cf81170..744af1e62 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 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();_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 ';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 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 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: '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 $('i[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":"AAAA,a,8oBAqBM,a,YACF,sBAAY,OAAZ,CAAqB,IAArB,CAA2B,oCACvB,KAAK,OAAL,CAAe,OAAf,CACA,KAAK,IAAL,CAAY,IACf,C,kEAES,CACN,KAAK,OAAL,CAAe,SAClB,C,uCAEQ,CACL,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAApB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAApB,CAEA,GAAI,eAAgB,WAAhB,CAA8B,WAA9B,IAAJ,CACA,GAAI,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,MAArB,IAAiC,MAArC,CAA6C,CACzC,WAAa,WAChB,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,eAFN,CAGD,KAAM,8CAHL,CAID,iBAAkB,IAJjB,CAKD,kBAAmB,IALlB,CAMD,eAAgB,KANf,CAOD,oBAAqB,IAPpB,CAQD,WAAY,UARX,CAAL,CASG,SAAC,GAAD,CAAS,CACR,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAFN,CAMH,YAAa,iCANV,CAOH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,sBAPG,CAQH,QAAS,KARN,CASH,KAAM,KAAK,SAAL,CAAe,CACjB,KAAM,GADW,CAAf,CATH,CAAP,EAYG,IAZH,CAYQ,cAAQ,CACZ,KAAK,KAAL,GACA,MAAM,IAAN,EACH,CAfD,EAeG,IAfH,CAeQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,EAFN,CAGD,KAAM,KAHL,CAAL,CAKH,CA1BD,CA2BH,CArCD,CAsCH,C,mCAEM,CACH,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAApB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAApB,CAEA,KAAK,CACD,KAAM,OADL,CAED,MAAO,WAFN,CAGD,KAAM,+CAHL,CAID,iBAAkB,IAJjB,CAKD,kBAAmB,IALlB,CAMD,eAAgB,KANf,CAOD,oBAAqB,IAPpB,CAQD,cAAe,WAAf,CAA6B,WAR5B,CAAL,CASG,SAAC,GAAD,CAAS,CACR,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAFN,CAMH,YAAa,iCANV,CAOH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,oBAPG,CAQH,QAAS,KARN,CASH,KAAM,KAAK,SAAL,CAAe,CACjB,QAAS,WAAT,CAAuB,WADN,CAEjB,MAAO,GAFU,CAAf,CATH,CAAP,EAaG,IAbH,CAaQ,cAAQ,CACZ,UAAU,MAAV,GAAmB,QAAnB,CAA4B,SAA5B,EAAuC,KAAvC,CAA6C,GAA7C,EAAkD,OAAlD,GACA,KAAK,KAAL,EACH,CAhBD,EAgBG,IAhBH,CAgBQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,EAFN,CAGD,KAAM,KAHL,CAAL,CAKH,CA3BD,CA4BH,CAtCD,CAwCH,C,uCAEQ,CACL,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,aAAc,UAAU,IAAV,CAAe,GAAf,CAApB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAApB,CACA,GAAM,uFACwD,WADxD,4GAAN,CAKA,UAAU,IAAV,CAAe,YAAf,EACA,GAAM,YAAa,UAAU,IAAV,CAAe,OAAf,CAAnB,CACA,GAAM,aAAc,UAAU,IAAV,CAAe,eAAf,CAApB,CAEA,WAAW,KAAX,GACA,WAAW,EAAX,CAAc,cAAd,CAA8B,WAAK,CAE/B,GACK,EAAE,IAAF,GAAW,SAAX,EAAwB,EAAE,KAAF,GAAY,EAArC,EACG,EAAE,IAAF,GAAW,MADd,EAEI,EAAE,IAAF,GAAW,SAAX,EAAwB,EAAE,KAAF,GAAY,EAApC,EAA0C,cAAgB,WAAW,GAAX,EAHlE,CAIE,CACE,GAAI,CAAC,EAAE,OAAF,CAAU,WAAV,CAAL,CAA6B,CACzB,UAAU,IAAV,CAAe,WAAf,CACH,CAFD,IAEO,CACH,UAAU,IAAV,CAAe,WAAf,CACH,CACD,WAAW,MAAX,GACA,YAAY,MAAZ,GAAqB,GAArB,GACA,MACH,CAED,GAAI,EAAE,IAAF,GAAW,SAAX,EAAwB,EAAE,KAAF,GAAY,EAAxC,CAA4C,OAE5C,YAAY,IAAZ,GACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAApB,CAEA,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAFN,CAMH,YAAa,iCANV,CAOH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,sBAPG,CAQH,QAAS,KARN,CASH,KAAM,KAAK,SAAL,CAAe,CACjB,QAAS,WAAT,CAAuB,WADN,CAEjB,MAAO,WAAP,CAAqB,WAAW,GAAX,EAFJ,CAAf,CATH,CAAP,EAaG,IAbH,CAaQ,cAAQ,CACZ,UAAU,IAAV,CAAe,WAAf,CAA4B,WAAW,GAAX,EAA5B,EACA,GAAI,CAAC,EAAE,OAAF,CAAU,WAAV,CAAL,CAA6B,CACzB,GAAI,SAAU,YAAY,IAAZ,CAAiB,MAAjB,CAAd,CACA,GAAI,UAAU,MAAV,GAAmB,IAAnB,CAAwB,MAAxB,IAAoC,QAAxC,CAAkD,CAC9C,QAAU,QAAQ,MAAR,CAAe,CAAf,CAAkB,QAAQ,WAAR,CAAoB,GAApB,CAAlB,EAA8C,GAA9C,CAAoD,WAAW,GAAX,EACjE,CACD,YAAY,IAAZ,CAAiB,MAAjB,CAAyB,OAAzB,EACA,UAAU,IAAV,CACI,YAAY,IAAZ,CAAiB,WAAW,GAAX,EAAjB,CADJ,CAGH,CATD,IASO,CACH,UAAU,IAAV,CAAe,WAAW,GAAX,EAAf,CACH,CACD,WAAW,MAAX,EACH,CA5BD,EA4BG,IA5BH,CA4BQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,UAAU,QAAV,CAAmB,WAAnB,EAAgC,KAAhC,CAAsC,IAAtC,EAA4C,KAA5C,CAAkD,UAAM,CACpD,UAAU,WAAV,CAAsB,WAAtB,EAAmC,OAAnC,EACH,CAFD,EAGA,WAAW,OAAX,CAAmB,CACf,UAAW,IADI,CAEf,UAAW,KAFI,CAGf,QAAS,KAHM,CAIf,MAAO,YAJQ,CAAnB,EAKG,OALH,CAKW,MALX,CAMH,CA3CD,EA2CG,MA3CH,CA2CU,UAAM,CACZ,YAAY,MAAZ,GACA,YAAY,MAAZ,GAAqB,GAArB,EACH,CA9CD,CA+CH,CArED,CAsEH,C,mCAEM,CACH,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAApB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAApB,CAEA,KAAK,CACD,KAAM,OADL,CAED,MAAO,WAFN,CAGD,KAAM,sDAHL,CAID,iBAAkB,IAJjB,CAKD,kBAAmB,IALlB,CAMD,eAAgB,KANf,CAOD,oBAAqB,IAPpB,CAQD,cAAe,WAAf,CAA6B,WAR5B,CAAL,CASG,SAAC,GAAD,CAAS,CACR,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAFN,CAMH,YAAa,iCANV,CAOH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,oBAPG,CAQH,QAAS,KARN,CASH,KAAM,KAAK,SAAL,CAAe,CACjB,QAAS,WAAT,CAAuB,WADN,CAEjB,MAAO,GAFU,CAAf,CATH,CAAP,EAaG,IAbH,CAaQ,cAAQ,CACZ,KAAK,CACD,KAAM,SADL,CAED,MAAO,EAFN,CAGD,KAAM,2BAHL,CAAL,EAKA,MAAM,IAAN,EACH,CApBD,EAoBG,IApBH,CAoBQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,EAFN,CAGD,KAAM,KAHL,CAAL,CAKH,CA/BD,CAgCH,CA1CD,CA2CH,C,2CAEU,CACP,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAAjB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAjB,CAEA,OAAO,QAAP,YAA6B,YAAY,MAAZ,CAAmB,SAAhD,oBAA4E,QAA5E,CAAuF,QAC1F,C,wCAEQ,CACL,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,SAAU,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAhB,CACA,GAAM,SAAU,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAhB,CAEA,KAAK,CACD,KAAM,SADL,CAED,MAAO,EAFN,CAGD,KAAM,yCAA2C,OAA3C,CAAqD,8DAH1D,CAID,KAAM,IAJL,CAKD,iBAAkB,IALjB,CAMD,kBAAmB,IANlB,CAOD,eAAgB,KAPf,CAQD,oBAAqB,IARpB,CAAL,CASG,UAAM,CACL,EAAE,IAAF,CAAO,CACH,KAAM,QADH,CAEH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,mBAA6G,OAA7G,CAAuH,OAFpH,CAGH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAHN,CAAP,EAOG,IAPH,CAOQ,cAAQ,CACZ,UAAU,MAAV,GAAmB,QAAnB,CAA4B,SAA5B,EAAuC,KAAvC,CAA6C,GAA7C,EAAkD,OAAlD,GACA,KAAK,CACD,KAAM,SADL,CAED,MAAO,cAFN,CAAL,CAIH,CAbD,EAaG,IAbH,CAaQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,KAAK,CACD,KAAM,OADL,CAED,MAAO,SAFN,CAGD,KAAM,IAHL,CAID,KAAM,0EAJL,CAAL,CAMH,CArBD,CAsBH,CAhCD,CAiCH,C,+CAEY,CACT,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAjB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAjB,CAEA,KAAK,CACD,MAAO,wDADN,CAED,KAAM,4CAFL,CAGD,KAAM,IAHL,CAID,kBAAmB,KAJlB,CAKD,eAAgB,KALf,CAMD,kBAAmB,KANlB,CAAL,EASA,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,0BAFG,CAGH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAHN,CAOH,YAAa,iCAPV,CAQH,KAAM,KAAK,SAAL,CAAe,CACjB,SAAU,QAAV,CAAqB,QADJ,CAAf,CARH,CAAP,EAWG,IAXH,CAWQ,cAAQ,CACZ,KAAK,KAAL,GACA,MAAM,IAAN,CAAW,QAAX,CACH,CAdD,EAcG,IAdH,CAcQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,SAFN,CAGD,KAAM,IAHL,CAID,KAAM,KAJL,CAAL,CAMH,CA1BD,CA2BH,C,2CAEU,CACP,GAAM,WAAY,EAAE,KAAK,OAAP,EAAgB,IAAhB,CAAqB,4BAArB,CAAlB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAjB,CACA,GAAM,UAAW,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAAjB,CAEA,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,IAAQ,YAAY,IAAZ,CAAiB,MAAzB,OAAqC,YAAY,IAAZ,CAAiB,IAAtD,KAA8D,YAAY,IAAZ,CAAiB,YAA/E,wBAFG,CAGH,QAAS,CACL,iBAAkB,YAAY,MAAZ,CAAmB,YADhC,CAEL,kBAAmB,YAAY,MAAZ,CAAmB,IAFjC,CAHN,CAOH,YAAa,iCAPV,CAQH,KAAM,KAAK,SAAL,CAAe,CACjB,SAAU,QAAV,CAAqB,QADJ,CAEjB,GAAI,SAAS,QAAT,EAFa,CAAf,CARH,CAAP,EAYG,IAZH,CAYQ,cAAQ,CACZ,MAAM,IAAN,CAAW,QAAX,CAAqB,aAAO,CACxB,GAAI,GAAJ,CAAS,OACT,GAAM,aAAc,EAAE,eAAF,EAAmB,IAAnB,gBAAuC,KAAK,QAA5C,OAA0D,MAA1D,EAApB,CACA,YAAY,QAAZ,CAAqB,iBAArB,EAAwC,KAAxC,CAA8C,IAA9C,EAAoD,KAApD,CAA0D,UAAM,CAC5D,YAAY,WAAZ,CAAwB,iBAAxB,EAA2C,OAA3C,EACH,CAFD,CAGH,CAND,CAOH,CApBD,EAoBG,IApBH,CAoBQ,eAAS,CACb,QAAQ,KAAR,CAAc,KAAd,EACA,GAAI,OAAQ,wDAAZ,CACA,GAAI,MAAO,OAAM,YAAb,GAA8B,WAA9B,EAA6C,MAAO,OAAM,YAAN,CAAmB,KAA1B,GAAoC,WAArF,CAAkG,CAC9F,MAAQ,MAAM,YAAN,CAAmB,KAC9B,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,SAFN,CAGD,KAAM,IAHL,CAID,KAAM,KAJL,CAAL,CAMH,CAhCD,CAiCH,C;;ACxYL,a,8oBAqBM,iB,YACF,2BAAc,wCACV,KAAK,UAAL,CAAkB,IACrB,C,8DAEK,CACF,KAAK,cAAL,GACA,KAAK,UAAL,EACH,C,0CAEQ,M,CAAQ,CACb,EAAE,QAAF,EAAY,IAAZ,CAAiB,iBAAjB,EAAoC,MAApC,GACA,GAAI,CAAC,EAAE,MAAF,CAAS,KAAK,UAAd,CAAL,CAAgC,KAAK,UAAL,CAAgB,WAAhB,CAA4B,QAA5B,EAEhC,GAAI,aAAc,EAAE,iBAAF,EAAqB,IAArB,CAA0B,iBAA1B,CAAlB,CACA,GAAI,OAAO,IAAP,CAAY,MAAZ,IAAwB,QAA5B,CAAsC,CAClC,GAAM,WAAY,OAAO,IAAP,CAAY,4BAAZ,CAAlB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,WAAf,CAAnB,CAApB,CACA,GAAM,aAAc,mBAAmB,UAAU,IAAV,CAAe,MAAf,CAAnB,CAApB,CACA,eAAiB,WAAjB,CAA+B,WAClC,CAED,GAAI,WAAY,kFAAhB,CAEA,GAAI,YAAY,WAAZ,CAAwB,SAA5B,CAAuC,CACnC,WAAa,iPAEhB,CAED,GAAI,YAAY,WAAZ,CAAwB,SAA5B,CAAuC,CACnC,WAAa,kGAChB,CAED,GAAI,YAAY,WAAZ,CAAwB,aAA5B,CAA2C,CACvC,WAAa,kIAChB,CAED,GAAI,YAAY,WAAZ,CAAwB,eAA5B,CAA6C,CACzC,WAAa,8HAChB,CAED,GAAI,YAAY,WAAZ,CAAwB,WAA5B,CAAyC,CACrC,WAAa,+FAC4C,YAAY,MAAZ,CAAmB,SAD/D,CAC0E,kBAD1E,CAC+F,WAD/F,CAC6G,6MAE7H,CAED,GAAI,YAAY,WAAZ,CAAwB,aAAxB,EAAyC,YAAY,WAAZ,CAAwB,WAArE,CAAkF,CAC9E,WAAa,2BAChB,CAED,GAAI,YAAY,WAAZ,CAAwB,aAA5B,CAA2C,CACvC,WAAa,4HAChB,CAED,GAAI,YAAY,WAAZ,CAAwB,WAA5B,CAAyC,CACrC,WAAa,0HAChB,CAED,WAAa,OAAb,CACA,MAAO,UACV,C,+CAEY,gBACT,EAAE,4BAAF,EAAgC,EAAhC,CAAmC,WAAnC,CAAgD,eAAS,CACrD,MAAM,cAAN,GACA,GAAI,EAAE,QAAF,EAAY,IAAZ,CAAiB,iBAAjB,EAAoC,EAApC,CAAuC,UAAvC,CAAJ,CAAwD,CACpD,EAAE,MAAF,EAAU,OAAV,CAAkB,OAAlB,EACA,MACH,CACD,MAAK,QAAL,CAAc,KAAd,CACH,CAPD,EAQA,EAAE,0BAAF,EAA8B,EAA9B,CAAiC,aAAjC,CAAgD,eAAS,CACrD,MAAK,QAAL,CAAc,KAAd,CACH,CAFD,CAGH,C,0CAEQ,K,CAAO,iBACZ,GAAM,QAAS,EAAE,MAAM,MAAR,EAAgB,OAAhB,CAAwB,IAAxB,CAAf,CACA,GAAM,MAAO,EAAE,KAAK,QAAL,CAAc,MAAd,CAAF,CAAb,CAEA,GAAI,OAAO,IAAP,CAAY,MAAZ,IAAwB,UAA5B,CAAwC,OACxC,MAAM,cAAN,GAEA,EAAE,IAAF,EAAQ,QAAR,CAAiB,MAAjB,EACA,EAAE,IAAF,EAAQ,IAAR,CAAa,WAAb,CAA0B,EAAE,MAAM,MAAR,CAA1B,EAA2C,IAA3C,GAAkD,GAAlD,CAAsD,CAClD,SAAU,UADwC,CAElD,KAAM,MAAM,KAAN,CAAc,GAF8B,CAGlD,IAAK,MAAM,KAHuC,CAAtD,EAMA,KAAK,UAAL,CAAkB,MAAlB,CACA,KAAK,UAAL,CAAgB,QAAhB,CAAyB,QAAzB,EAGA,GAAM,SAAU,GAAI,aAAJ,CAAiB,MAAjB,CAAyB,IAAzB,CAAhB,CACA,GAAI,YAAY,WAAZ,CAAwB,SAA5B,CAAuC,CACnC,EAAE,IAAF,EAAQ,IAAR,CAAa,wBAAb,EAAuC,MAAvC,GAAgD,EAAhD,CAAmD,OAAnD,CAA4D,WAAK,CAC7D,EAAE,cAAF,GACA,QAAQ,IAAR,EACH,CAHD,EAIA,EAAE,IAAF,EAAQ,IAAR,CAAa,0BAAb,EAAyC,MAAzC,GAAkD,EAAlD,CAAqD,OAArD,CAA8D,WAAK,CAC/D,EAAE,cAAF,GACA,QAAQ,MAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,SAA5B,CAAuC,CACnC,EAAE,IAAF,EAAQ,IAAR,CAAa,wBAAb,EAAuC,MAAvC,GAAgD,EAAhD,CAAmD,OAAnD,CAA4D,WAAK,CAC7D,EAAE,cAAF,GACA,QAAQ,IAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,aAA5B,CAA2C,CACvC,GAAI,OAAO,IAAP,CAAY,MAAZ,IAAwB,QAA5B,CAAsC,CAClC,EAAE,IAAF,EAAQ,IAAR,CAAa,4BAAb,EAA2C,WAA3C,CAAuD,QAAvD,CACH,CACD,EAAE,IAAF,EAAQ,IAAR,CAAa,4BAAb,EAA2C,MAA3C,GAAoD,EAApD,CAAuD,OAAvD,CAAgE,WAAK,CACjE,EAAE,cAAF,GACA,QAAQ,QAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,eAA5B,CAA6C,CACzC,GAAI,EAAE,OAAF,CAAU,CAAC,iBAAD,CAAoB,kBAApB,CAAwC,oBAAxC,CAAV,CAAyE,OAAO,IAAP,CAAY,MAAZ,CAAzE,EAA8F,MAA9F,CAAuG,CAA3G,CAA8G,CAC1G,EAAE,IAAF,EAAQ,IAAR,CAAa,8BAAb,EAA6C,WAA7C,CAAyD,QAAzD,CACH,CACD,EAAE,IAAF,EAAQ,IAAR,CAAa,8BAAb,EAA6C,MAA7C,GAAsD,EAAtD,CAAyD,OAAzD,CAAkE,WAAK,CACnE,EAAE,cAAF,GACA,QAAQ,UAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,WAA5B,CAAyC,CACrC,EAAE,IAAF,EAAQ,IAAR,CAAa,0BAAb,EAAyC,MAAzC,GAAkD,EAAlD,CAAqD,OAArD,CAA8D,WAAK,CAC/D,EAAE,cAAF,GACA,QAAQ,MAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,aAA5B,CAA2C,CACvC,GAAI,OAAO,IAAP,CAAY,MAAZ,IAAwB,MAA5B,CAAoC,CAChC,EAAE,IAAF,EAAQ,IAAR,CAAa,4BAAb,EAA2C,WAA3C,CAAuD,QAAvD,CACH,CACD,EAAE,IAAF,EAAQ,IAAR,CAAa,4BAAb,EAA2C,MAA3C,GAAoD,EAApD,CAAuD,OAAvD,CAAgE,WAAK,CACjE,EAAE,cAAF,GACA,QAAQ,QAAR,EACH,CAHD,CAIH,CAED,GAAI,YAAY,WAAZ,CAAwB,WAA5B,CAAyC,CACrC,EAAE,IAAF,EAAQ,IAAR,CAAa,0BAAb,EAAyC,MAAzC,GAAkD,EAAlD,CAAqD,OAArD,CAA8D,WAAK,CAC/D,EAAE,cAAF,GACA,QAAQ,MAAR,EACH,CAHD,CAIH,CAED,EAAE,MAAF,EAAU,MAAV,GAAmB,EAAnB,CAAsB,OAAtB,CAA+B,eAAS,CACpC,GAAG,EAAE,MAAM,MAAR,EAAgB,EAAhB,CAAmB,oBAAnB,CAAH,CAA6C,CACzC,MAAM,cAAN,GACA,MACH,CACD,EAAE,IAAF,EAAQ,MAAR,GAAiB,MAAjB,GACA,GAAG,CAAC,EAAE,MAAF,CAAS,OAAK,UAAd,CAAJ,CAA+B,OAAK,UAAL,CAAgB,WAAhB,CAA4B,QAA5B,CAClC,CAPD,CAQH,C,uDAEgB,CACb,EAAE,iCAAF,EAAqC,EAArC,CAAwC,OAAxC,CAAiD,SAAU,KAAV,CAAiB,CAC9D,MAAM,cAAN,GAEA,GAAM,MAAO,EAAE,IAAF,EAAQ,MAAR,GAAiB,IAAjB,CAAsB,MAAtB,GAAiC,EAA9C,CACA,GAAM,MAAO,EAAE,IAAF,EAAQ,MAAR,GAAiB,IAAjB,CAAsB,MAAtB,GAAiC,EAA9C,CAEA,OAAO,QAAP,CAAgB,IAAhB,CAAuB,mBAAmB,KAAO,IAA1B,CAAvB,CACA,MAAM,IAAN,EACH,CARD,CASH,C,+BAGL,OAAO,WAAP,CAAqB,GAAI,iBAAzB;AC1MA,a,q3BAqBM,Y,YACF,sBAAc,mCACV,KAAK,IAAL,CAAU,KAAK,UAAL,EAAV,CACH,C,0DAEI,I,CAAM,I,CAAM,gBACb,GAAI,EAAE,WAAF,CAAc,IAAd,CAAJ,CAAyB,CACrB,KAAO,KAAK,UAAL,EACV,CAED,KAAK,MAAL,CAAY,IAAZ,EACA,EAAE,IAAF,CAAO,CACH,KAAM,MADH,CAEH,IAAK,YAAY,IAAZ,CAAiB,aAFnB,CAGH,QAAS,CACL,eAAgB,YAAY,IAAZ,CAAiB,SAD5B,CAHN,CAMH,KAAM,CACF,UAAW,IADT,CANH,CAAP,EASG,IATH,CASQ,cAAQ,CACZ,MAAK,MAAL,CAAY,KAAZ,EACA,EAAE,aAAF,EAAiB,OAAjB,CAAyB,EAAzB,EAA6B,IAA7B,CAAkC,IAAlC,EAAwC,SAAxC,CAAkD,EAAlD,CAAsD,UAAM,CACxD,YAAY,GAAZ,GACA,MAAK,iBAAL,GACA,GAAI,EAAE,UAAF,CAAa,IAAb,CAAJ,CAAwB,CACpB,MAAO,OACV,CACJ,CAND,EAOA,EAAE,iBAAF,EAAqB,OAArB,GAEA,GAAI,OAAO,MAAP,mCAAO,KAAP,KAAiB,QAArB,CAA+B,CAC3B,MAAM,aAAN,CAAoB,SAAS,cAAT,CAAwB,oBAAxB,CAApB,CACH,CACJ,CAvBD,EAuBG,IAvBH,CAuBQ,eAAS,CACb,MAAK,MAAL,CAAY,KAAZ,EACA,GAAI,EAAE,UAAF,CAAa,IAAb,CAAJ,CAAwB,CACpB,MAAO,MAAK,GAAI,MAAJ,CAAU,8BAAV,CAAL,CACV,CACD,KAAK,CACD,KAAM,OADL,CAED,MAAO,YAFN,CAGD,KAAM,8EAHL,CAAL,EAKA,QAAQ,KAAR,CAAc,KAAd,CACH,CAlCD,CAmCH,C,sCAEM,I,CAAM,CACT,GAAI,IAAJ,CAAS,CACL,EAAE,eAAF,EAAmB,MAAnB,CAA0B,GAA1B,CACH,CAFD,IAEO,CACH,EAAE,eAAF,EAAmB,OAAnB,CAA2B,GAA3B,CACH,CACJ,C,6DAEmB,iBAChB,EAAE,+BAAF,EAAmC,MAAnC,GAA4C,EAA5C,CAA+C,OAA/C,CAAwD,UAAM,CAC1D,EAAE,+BAAF,EAAmC,QAAnC,CAA4C,SAA5C,EACA,OAAK,IAAL,EACH,CAHD,CAIH,C,+CAEY,CACT,MAAO,oBAAmB,OAAO,QAAP,CAAgB,IAAhB,CAAqB,SAArB,CAA+B,CAA/B,CAAnB,CACV,C,0BAIL,OAAO,KAAP,CAAe,GAAI,YAAnB","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() {\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 let inputValue = `${currentPath}${currentName}/`;\n if ($(this.element).data('type') === 'file') {\n inputValue = currentPath;\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 = $('#headerTableRow').attr('data-currentDir');\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 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: '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 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/contextmenu.js b/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js index 8b9b3c2eb..0e6904385 100644 --- a/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js +++ b/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js @@ -85,6 +85,10 @@ class ContextMenuClass { rightClick() { $('[data-action="toggleMenu"]').on('mousedown', event => { event.preventDefault(); + if ($(document).find('#fileOptionMenu').is(':visible')) { + $('body').trigger('click'); + return; + } this.showMenu(event); }); $('#file_listing > tbody td').on('contextmenu', event => { diff --git a/public/themes/pterodactyl/js/frontend/serverlist.js b/public/themes/pterodactyl/js/frontend/serverlist.js index 019562eb0..0490b7fbe 100644 --- a/public/themes/pterodactyl/js/frontend/serverlist.js +++ b/public/themes/pterodactyl/js/frontend/serverlist.js @@ -27,35 +27,47 @@ var ServerList = (function () { 3: 'Stopping' }; - function updateServerStatus () { - $('.dynamic-update').each(function (index, data) { - var element = $(this); - var serverShortUUID = $(this).data('server'); - $.ajax({ - type: 'GET', - url: Router.route('server.ajax.status', { server: serverShortUUID }), - timeout: 5000, - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - if (typeof data.status === 'undefined') { - element.find('[data-action="status"]').html('Error'); - return; - } - switch (data.status) { - case 0: - element.find('[data-action="status"]').html('Offline'); - break; - case 1: - element.find('[data-action="status"]').html('Online'); - break; - case 2: - element.find('[data-action="status"]').html('Starting'); - break; - case 3: - element.find('[data-action="status"]').html('Stopping'); - break; + $('.dynamic-update').each(function (index, data) { + var element = $(this); + var serverShortUUID = $(this).data('server'); + + $.ajax({ + type: 'GET', + url: Router.route('server.ajax.status', { server: serverShortUUID }), + timeout: 5000, + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + } + }).done(function (data) { + if (typeof data.status === 'undefined') { + element.find('[data-action="status"]').html('Error'); + return; + } + switch (data.status) { + case 0: + element.find('[data-action="status"]').html('Offline'); + break; + case 1: + element.find('[data-action="status"]').html('Online'); + break; + case 2: + element.find('[data-action="status"]').html('Starting'); + break; + case 3: + element.find('[data-action="status"]').html('Stopping'); + break; + case 20: + element.find('[data-action="status"]').html('Installing'); + break; + case 30: + element.find('[data-action="status"]').html('Suspended'); + break; + } + if (data.status > 0 && data.status < 4) { + var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); + var currentCpu = data.proc.cpu.total; + if (cpuMax !== 0) { + currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); } if (data.status !== 0) { var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); diff --git a/public/themes/pterodactyl/vendor/adminlte/app.min.js b/public/themes/pterodactyl/vendor/adminlte/app.min.js index 4851def3e..7efb107e1 100755 --- a/public/themes/pterodactyl/vendor/adminlte/app.min.js +++ b/public/themes/pterodactyl/vendor/adminlte/app.min.js @@ -10,4 +10,4 @@ * @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-toggle='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 +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 diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 0d2e305a8..3748167f6 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -15,4 +15,6 @@ return [ '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.', 'request_reset' => 'Locate Account', + '2fa_required' => '2-Factor Authentication', + '2fa_failed' => 'The 2FA token provided was invalid.', ]; diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index d1862f0ba..ee2939f31 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -2,6 +2,7 @@ return [ 'email' => 'Email', + 'user_identifier' => 'Username or Email', 'password' => 'Password', 'confirm_password' => 'Confirm Password', 'login' => 'Login', @@ -60,4 +61,6 @@ return [ 'no' => 'No', 'delete' => 'Delete', '2fa' => '2FA', + 'logout' => 'Logout', + 'admin_cp' => 'Admin Control Panel', ]; diff --git a/resources/themes/pterodactyl/auth/login.blade.php b/resources/themes/pterodactyl/auth/login.blade.php index 82ff3d564..014e848bb 100644 --- a/resources/themes/pterodactyl/auth/login.blade.php +++ b/resources/themes/pterodactyl/auth/login.blade.php @@ -47,7 +47,7 @@
    - +
    @@ -57,7 +57,7 @@
    - +
    diff --git a/resources/views/emails/added-subuser.blade.php b/resources/themes/pterodactyl/auth/totp.blade.php similarity index 56% rename from resources/views/emails/added-subuser.blade.php rename to resources/themes/pterodactyl/auth/totp.blade.php index c6d0d47d2..4a021603d 100644 --- a/resources/views/emails/added-subuser.blade.php +++ b/resources/themes/pterodactyl/auth/totp.blade.php @@ -17,12 +17,30 @@ {{-- 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 - You've been added to a Server - - -

    Pterodactyl - Added to Server

    -

    You are recieving this email because you have been added as a subuser for {{ $serverName }} on Pterodactyl Panel.

    - - +@extends('layouts.auth') + +@section('title') + 2FA Checkpoint +@endsection + +@section('content') + +@endsection diff --git a/resources/themes/pterodactyl/base/api/index.blade.php b/resources/themes/pterodactyl/base/api/index.blade.php index 6509d891f..d0ec97c9b 100644 --- a/resources/themes/pterodactyl/base/api/index.blade.php +++ b/resources/themes/pterodactyl/base/api/index.blade.php @@ -79,3 +79,48 @@
    @endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/base/index.blade.php b/resources/themes/pterodactyl/base/index.blade.php index c6066c25a..71efda93f 100644 --- a/resources/themes/pterodactyl/base/index.blade.php +++ b/resources/themes/pterodactyl/base/index.blade.php @@ -62,8 +62,8 @@ {{ $server->uuidShort }} {{ $server->name }} - {{ $server->nodeName }} - @if(!is_null($server->ip_alias)){{ $server->ip_alias }}@else{{ $server->ip }}@endif:{{ $server->port }} + {{ $server->node->name }} + {{ $server->allocation->alias }}:{{ $server->allocation->port }} -- / {{ $server->memory === 0 ? '∞' : $server->memory }} MB -- % diff --git a/resources/themes/pterodactyl/layouts/auth.blade.php b/resources/themes/pterodactyl/layouts/auth.blade.php index 3e6ea1237..a063ed1df 100644 --- a/resources/themes/pterodactyl/layouts/auth.blade.php +++ b/resources/themes/pterodactyl/layouts/auth.blade.php @@ -44,10 +44,12 @@
    @yield('content')

    - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software & Design.
    + Copyright © 2015 - {{ date('Y') }} Pterodactyl Software.

    {!! Theme::js('vendor/jquery/jquery.min.js') !!} {!! Theme::js('vendor/bootstrap/bootstrap.min.js') !!} + + @if(config('app.phrase_in_context')) {!! Theme::js('js/phraseapp.js') !!} @endif diff --git a/resources/themes/pterodactyl/layouts/error.blade.php b/resources/themes/pterodactyl/layouts/error.blade.php index 1e9ab8dda..ca946906b 100644 --- a/resources/themes/pterodactyl/layouts/error.blade.php +++ b/resources/themes/pterodactyl/layouts/error.blade.php @@ -60,7 +60,7 @@ - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software & Design. + Copyright © 2015 - {{ date('Y') }} Pterodactyl Software. @section('footer-scripts') diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php index 32a922416..b5dede0f2 100644 --- a/resources/themes/pterodactyl/layouts/master.blade.php +++ b/resources/themes/pterodactyl/layouts/master.blade.php @@ -57,47 +57,21 @@ @@ -116,7 +90,7 @@ @@ -236,7 +228,7 @@ - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software & Design. + Copyright © 2015 - {{ date('Y') }} Pterodactyl Software.