diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9126443..d7357ca1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v0.7.6 (Derelict Dermodactylus) +### Fixed +* Fixes a UI error when attempting to change the default Nest and Egg for an existing server. +* Correct permissions check in UI to allow subusers with permission to `view-allocations` the ability to actually see the sidebar link. +* Fixes improper behavior when marking an egg as copying the configuration from another. +* Debug bar is only checked when the app is set to debug mode in the API session handler, rather than when it is in local mode to match the plugin settings. +* Added validation to port allocations to prevent allocation of restricted or invalid ports. +* Fix data integrity exception thrown when attempting to store updated server egg variables. +* Added missing permissions check on 'SFTP Configuration' page to ensure user has permission to access a server's SFTP server before showing a user credentials. + +### Added +* Added ability for end users to change the name of their server through the UI. This option is only open to the server owner or an admin. +* Added giant warning message if you attempt to change an encryption key once one has been set. + +### Changed +* Panel now throws proper 504: Gateway Timeout errors on server listing when daemon is offline. +* Sessions handled through redis now use a seperate database (default `1`) to store session database to avoid logging users out when flushing the cache. +* File manager UI improved to be clearer with buttons and cleaner on mobile. +* reCAPTCHA's secret key position swapped with website key in advanced panel settings to be consistent with Google's reCAPTCHA dashboard. +* Changed DisplayException to handle its own logging correctly and check if the previous exception is marked as one that should not be logged. +* Changed 'New Folder' modal in file manager to include a trailing slash. + ## v0.7.5 (Derelict Dermodactylus) ### Fixed * Fixes application API keys being created as a client API key. diff --git a/Vagrantfile b/Vagrantfile index ed4b5bd4c..1eb62f5dc 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,13 +1,9 @@ Vagrant.configure("2") do |config| - config.vm.box = "ubuntu/xenial64" + config.vm.box = "bento/ubuntu-16.04" config.vm.synced_folder "./", "/var/www/html/pterodactyl", owner: "www-data", group: "www-data" - #config.vm.provision :file, source: ".dev/vagrant/pterdactyl.conf", destination: "/etc/nginx/sites-available/pterodactyl.conf" - #config.vm.provision :file, source: ".dev/vagrant/pteroq.service", destination: "/etc/systemd/system/pteroq.service" - #config.vm.provision :file, source: ".dev/vagrant/mailhog.service", destination: "/etc/systemd/system/mailhog.service" - #config.vm.provision :file, source: ".dev/vagrant/.env", destination: "/var/www/html/pterodactyl/.env" config.vm.provision :shell, path: ".dev/vagrant/provision.sh" config.vm.network :private_network, ip: "192.168.50.2" @@ -16,6 +12,6 @@ Vagrant.configure("2") do |config| config.vm.network :forwarded_port, guest: 3306, host: 53306 # Config for the vagrant-dns plugin (https://github.com/BerlinVagrant/vagrant-dns) - config.dns.tld = "app" - config.dns.patterns = [/^pterodactyl.app$/] + config.dns.tld = "test" + config.dns.patterns = [/^pterodactyl.test$/] end diff --git a/app/Console/Commands/Overrides/KeyGenerateCommand.php b/app/Console/Commands/Overrides/KeyGenerateCommand.php new file mode 100644 index 000000000..aa9294999 --- /dev/null +++ b/app/Console/Commands/Overrides/KeyGenerateCommand.php @@ -0,0 +1,28 @@ +input->isInteractive()) { + $this->output->warning(trans('command/messages.key.warning')); + if (! $this->confirm(trans('command/messages.key.confirm'))) { + return; + } + + if (! $this->confirm(trans('command/messages.key.final_confirm'))) { + return; + } + } + + parent::handle(); + } +} diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index e3a839c5e..a00939711 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -2,9 +2,11 @@ namespace Pterodactyl\Exceptions; -use Log; +use Exception; use Throwable; +use Psr\Log\LoggerInterface; use Illuminate\Http\Response; +use Illuminate\Container\Container; use Prologue\Alerts\AlertsMessageBag; class DisplayException extends PterodactylException @@ -31,10 +33,6 @@ class DisplayException extends PterodactylException { parent::__construct($message, $code, $previous); - if (! is_null($previous)) { - Log::{$level}($previous); - } - $this->level = $level; } @@ -70,8 +68,31 @@ class DisplayException extends PterodactylException ]), method_exists($this, 'getStatusCode') ? $this->getStatusCode() : Response::HTTP_BAD_REQUEST); } - app()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + Container::getInstance()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); return redirect()->back()->withInput(); } + + /** + * Log the exception to the logs using the defined error level only if the previous + * exception is set. + * + * @return mixed + * + * @throws \Exception + */ + public function report() + { + if (! $this->getPrevious() instanceof Exception || ! Handler::isReportable($this->getPrevious())) { + return null; + } + + try { + $logger = Container::getInstance()->make(LoggerInterface::class); + } catch (Exception $ex) { + throw $this->getPrevious(); + } + + return $logger->{$this->getErrorLevel()}($this->getPrevious()); + } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 500f2d0ca..be8a4e556 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Exceptions; use Exception; use PDOException; use Psr\Log\LoggerInterface; +use Illuminate\Container\Container; use Illuminate\Auth\AuthenticationException; use Illuminate\Session\TokenMismatchException; use Illuminate\Validation\ValidationException; @@ -24,7 +25,6 @@ class Handler extends ExceptionHandler protected $dontReport = [ AuthenticationException::class, AuthorizationException::class, - DisplayException::class, HttpException::class, ModelNotFoundException::class, RecordNotFoundException::class, @@ -201,6 +201,17 @@ class Handler extends ExceptionHandler return ['errors' => [array_merge($error, $override)]]; } + /** + * Return an array of exceptions that should not be reported. + * + * @param \Exception $exception + * @return bool + */ + public static function isReportable(Exception $exception): bool + { + return (new static(Container::getInstance()))->shouldReport($exception); + } + /** * Convert an authentication exception into an unauthenticated response. * diff --git a/app/Exceptions/Service/Allocation/CidrOutOfRangeException.php b/app/Exceptions/Service/Allocation/CidrOutOfRangeException.php new file mode 100644 index 000000000..4852dc106 --- /dev/null +++ b/app/Exceptions/Service/Allocation/CidrOutOfRangeException.php @@ -0,0 +1,16 @@ + $port])); + } +} diff --git a/app/Exceptions/Service/Allocation/PortOutOfRangeException.php b/app/Exceptions/Service/Allocation/PortOutOfRangeException.php new file mode 100644 index 000000000..695925f89 --- /dev/null +++ b/app/Exceptions/Service/Allocation/PortOutOfRangeException.php @@ -0,0 +1,16 @@ +daemonRepository->setServer($server)->setToken($token)->details(); + } catch (ConnectException $exception) { + throw new HttpException(Response::HTTP_GATEWAY_TIMEOUT, $exception->getMessage()); } catch (RequestException $exception) { throw new HttpException(500, $exception->getMessage()); } diff --git a/app/Http/Controllers/Server/Settings/NameController.php b/app/Http/Controllers/Server/Settings/NameController.php new file mode 100644 index 000000000..29cdb9ed9 --- /dev/null +++ b/app/Http/Controllers/Server/Settings/NameController.php @@ -0,0 +1,59 @@ +repository = $repository; + } + + /** + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request) + { + $this->authorize('view-name', $request->attributes->get('server')); + $this->setRequest($request)->injectJavascript(); + + return view('server.settings.name'); + } + + /** + * Update the stored name for a specific server. + * + * @param \Pterodactyl\Http\Requests\Server\Settings\ChangeServerNameRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(ChangeServerNameRequest $request): RedirectResponse + { + $this->repository->update($request->getServer()->id, $request->validated()); + + return redirect()->route('server.settings.name', $request->getServer()->uuidShort); + } +} diff --git a/app/Http/Controllers/Server/Settings/SftpController.php b/app/Http/Controllers/Server/Settings/SftpController.php index b128ba5c9..58b110bd3 100644 --- a/app/Http/Controllers/Server/Settings/SftpController.php +++ b/app/Http/Controllers/Server/Settings/SftpController.php @@ -16,9 +16,12 @@ class SftpController extends Controller * * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException */ public function index(Request $request): View { + $this->authorize('access-sftp', $request->attributes->get('server')); $this->setRequest($request)->injectJavascript(); return view('server.settings.sftp'); diff --git a/app/Http/Middleware/Api/SetSessionDriver.php b/app/Http/Middleware/Api/SetSessionDriver.php index c69311a65..3d5c16617 100644 --- a/app/Http/Middleware/Api/SetSessionDriver.php +++ b/app/Http/Middleware/Api/SetSessionDriver.php @@ -41,7 +41,7 @@ class SetSessionDriver */ public function handle(Request $request, Closure $next) { - if ($this->app->environment() !== 'production') { + if ($this->config->get('app.debug')) { $this->app->make(LaravelDebugbar::class)->disable(); } diff --git a/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php b/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php new file mode 100644 index 000000000..c969cb0e2 --- /dev/null +++ b/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php @@ -0,0 +1,31 @@ + Server::getCreateRules()['name'], + ]; + } +} diff --git a/app/Models/Egg.php b/app/Models/Egg.php index c9b2d9767..ade28239d 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -113,7 +113,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getCopyScriptInstallAttribute() { - return (is_null($this->copy_script_from)) ? $this->script_install : $this->scriptFrom->script_install; + if (! is_null($this->script_install) || is_null($this->copy_script_from)) { + return $this->script_install; + } + + return $this->scriptFrom->script_install; } /** @@ -124,7 +128,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getCopyScriptEntryAttribute() { - return (is_null($this->copy_script_from)) ? $this->script_entry : $this->scriptFrom->script_entry; + if (! is_null($this->script_entry) || is_null($this->copy_script_from)) { + return $this->script_entry; + } + + return $this->scriptFrom->script_entry; } /** @@ -135,7 +143,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getCopyScriptContainerAttribute() { - return (is_null($this->copy_script_from)) ? $this->script_container : $this->scriptFrom->script_container; + if (! is_null($this->script_container) || is_null($this->copy_script_from)) { + return $this->script_container; + } + + return $this->scriptFrom->script_container; } /** @@ -145,7 +157,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getInheritConfigFilesAttribute() { - return is_null($this->config_from) ? $this->config_files : $this->configFrom->config_files; + if (! is_null($this->config_files) || is_null($this->config_from)) { + return $this->config_files; + } + + return $this->configFrom->config_files; } /** @@ -155,7 +171,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getInheritConfigStartupAttribute() { - return is_null($this->config_from) ? $this->config_startup : $this->configFrom->config_startup; + if (! is_null($this->config_startup) || is_null($this->config_from)) { + return $this->config_startup; + } + + return $this->configFrom->config_startup; } /** @@ -165,7 +185,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getInheritConfigLogsAttribute() { - return is_null($this->config_from) ? $this->config_logs : $this->configFrom->config_logs; + if (! is_null($this->config_logs) || is_null($this->config_from)) { + return $this->config_logs; + } + + return $this->configFrom->config_logs; } /** @@ -175,7 +199,11 @@ class Egg extends Model implements CleansAttributes, ValidableContract */ public function getInheritConfigStopAttribute() { - return is_null($this->config_from) ? $this->config_stop : $this->configFrom->config_stop; + if (! is_null($this->config_stop) || is_null($this->config_from)) { + return $this->config_stop; + } + + return $this->configFrom->config_stop; } /** diff --git a/app/Models/Node.php b/app/Models/Node.php index 0134b7c7a..f978bcf77 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -164,7 +164,7 @@ class Node extends Model implements CleansAttributes, ValidableContract 'enabled' => true, 'kill_at_count' => 5, 'decay' => 10, - 'bytes' => 30720, + 'lines' => 1000, 'check_interval_ms' => 100, ], ], diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 10d58ef40..905bb776f 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -1,26 +1,24 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Allocations; use IPTools\Network; use Pterodactyl\Models\Node; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException; +use Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException; +use Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException; +use Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException; class AssignmentService { const CIDR_MAX_BITS = 27; const CIDR_MIN_BITS = 32; + const PORT_FLOOR = 1024; + const PORT_CEIL = 65535; const PORT_RANGE_LIMIT = 1000; - const PORT_RANGE_REGEX = '/^(\d{1,5})-(\d{1,5})$/'; + const PORT_RANGE_REGEX = '/^(\d{4,5})-(\d{4,5})$/'; /** * @var \Illuminate\Database\ConnectionInterface @@ -38,10 +36,8 @@ class AssignmentService * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository * @param \Illuminate\Database\ConnectionInterface $connection */ - public function __construct( - AllocationRepositoryInterface $repository, - ConnectionInterface $connection - ) { + public function __construct(AllocationRepositoryInterface $repository, ConnectionInterface $connection) + { $this->connection = $connection; $this->repository = $repository; } @@ -49,21 +45,20 @@ class AssignmentService /** * Insert allocations into the database and link them to a specific node. * - * @param int|\Pterodactyl\Models\Node $node - * @param array $data + * @param \Pterodactyl\Models\Node $node + * @param array $data * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException + * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function handle($node, array $data) + public function handle(Node $node, array $data) { - if ($node instanceof Node) { - $node = $node->id; - } - $explode = explode('/', $data['allocation_ip']); if (count($explode) !== 1) { if (! ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) { - throw new DisplayException(trans('exceptions.allocations.cidr_out_of_range')); + throw new CidrOutOfRangeException; } } @@ -71,7 +66,7 @@ class AssignmentService foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { foreach ($data['allocation_ports'] as $port) { if (! is_digit($port) && ! preg_match(self::PORT_RANGE_REGEX, $port)) { - throw new DisplayException(trans('exceptions.allocations.invalid_mapping', ['port' => $port])); + throw new InvalidPortMappingException($port); } $insertData = []; @@ -79,12 +74,16 @@ class AssignmentService $block = range($matches[1], $matches[2]); if (count($block) > self::PORT_RANGE_LIMIT) { - throw new DisplayException(trans('exceptions.allocations.too_many_ports')); + throw new TooManyPortsInRangeException; + } + + if ((int) $matches[1] <= self::PORT_FLOOR || (int) $matches[2] > self::PORT_CEIL) { + throw new PortOutOfRangeException; } foreach ($block as $unit) { $insertData[] = [ - 'node_id' => $node, + 'node_id' => $node->id, 'ip' => $ip->__toString(), 'port' => (int) $unit, 'ip_alias' => array_get($data, 'allocation_alias'), @@ -92,8 +91,12 @@ class AssignmentService ]; } } else { + if ((int) $port <= self::PORT_FLOOR || (int) $port > self::PORT_CEIL) { + throw new PortOutOfRangeException; + } + $insertData[] = [ - 'node_id' => $node, + 'node_id' => $node->id, 'ip' => $ip->__toString(), 'port' => (int) $port, 'ip_alias' => array_get($data, 'allocation_alias'), diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 4e954ae1f..9fa1390db 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -105,7 +105,7 @@ class StartupModificationService 'server_id' => $server->id, 'variable_id' => $result->id, ], [ - 'variable_value' => $result->value, + 'variable_value' => $result->value ?? '', ]); }); } diff --git a/artisan b/artisan index df630d0d6..5c23e2e24 100755 --- a/artisan +++ b/artisan @@ -1,6 +1,8 @@ #!/usr/bin/env php handle( | Shutdown The Application |-------------------------------------------------------------------------- | -| Once Artisan has finished running. We will fire off the shutdown events +| Once Artisan has finished running, we will fire off the shutdown events | so that any final work may be done by the application before we shut | down the process. This is the last thing to happen to the request. | diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php deleted file mode 100644 index 72354a655..000000000 --- a/bootstrap/autoload.php +++ /dev/null @@ -1,17 +0,0 @@ - '0.7.5', + 'version' => '0.7.6', /* |-------------------------------------------------------------------------- diff --git a/config/cache.php b/config/cache.php index 86bbeb61e..bf429c4bf 100644 --- a/config/cache.php +++ b/config/cache.php @@ -70,6 +70,12 @@ return [ 'driver' => 'redis', 'connection' => 'default', ], + + 'sessions' => [ + 'driver' => env('SESSION_DRIVER', 'database'), + 'table' => 'sessions', + 'connection' => env('SESSION_DRIVER') === 'redis' ? 'sessions' : null, + ], ], /* diff --git a/config/database.php b/config/database.php index acbc8627b..e03a09767 100644 --- a/config/database.php +++ b/config/database.php @@ -71,11 +71,19 @@ return [ 'redis' => [ 'client' => 'predis', + 'default' => [ 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DATBASE', 0), ], + + 'sessions' => [ + 'host' => env('REDIS_HOST', 'localhost'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATBASE_SESSIONS', 1), + ], ], ]; diff --git a/config/session.php b/config/session.php index 837809f66..4f3a2e6de 100644 --- a/config/session.php +++ b/config/session.php @@ -69,7 +69,7 @@ return [ | */ - 'connection' => null, + 'connection' => env('SESSION_DRIVER') === 'redis' ? 'sessions' : null, /* |-------------------------------------------------------------------------- diff --git a/database/seeds/eggs/minecraft/egg-forge-minecraft.json b/database/seeds/eggs/minecraft/egg-forge-minecraft.json index a4ab2c4b9..731d8844c 100644 --- a/database/seeds/eggs/minecraft/egg-forge-minecraft.json +++ b/database/seeds/eggs/minecraft/egg-forge-minecraft.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2017-11-03T22:15:10-05:00", + "exported_at": "2018-02-27T00:57:04-06:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -17,7 +17,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\n# Forge Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add curl\n\nGET_VERSIONS=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\.[0-9][0-9][0-9][0-9]')\nLATEST_VERSION=$(echo $GET_VERSIONS | sed 's\/ \/\/g')\n\ncd \/mnt\/server\n\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-installer.jar -o installer.jar\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-universal.jar -o server.jar\n\njava -jar installer.jar --installServer\nrm -rf installer.jar", + "script": "#!\/bin\/ash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\nGET_VERSIONS=$(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A1 Latest | grep -o -e '[1]\\.[0-9][0-9]]\\?\\.\\?[0-9]\\?[0-9] - [0-9][0-9]\\.[0-9][0-9]\\.[0-9]\\?[0-9]\\.[0-9][0-9][0-9][0-9]')\r\nLATEST_VERSION=$(echo $GET_VERSIONS | sed 's\/ \/\/g')\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-installer.jar -o installer.jar\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$LATEST_VERSION\/forge-$LATEST_VERSION-universal.jar -o server.jar\r\n\r\njava -jar installer.jar --installServer\r\nrm -rf installer.jar", "container": "frolvlad\/alpine-oraclejdk8:cleaned", "entrypoint": "ash" } @@ -33,4 +33,4 @@ "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" } ] -} \ No newline at end of file +} diff --git a/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json b/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json index fe73a1665..927978d23 100644 --- a/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json +++ b/database/seeds/eggs/voice-servers/egg-teamspeak3-server.json @@ -17,7 +17,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\n# TS3 Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add tar curl\n\ncd \/tmp\n\ncurl -sSLO http:\/\/dl.4players.de\/ts\/releases\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\n\ntar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\ncp -r teamspeak3-server_linux_amd64\/* \/mnt\/server\n\necho \"machine_id=\ndefault_voice_port=${SERVER_PORT}\nvoice_ip=0.0.0.0\nlicensepath=\nfiletransfer_port=30033\nfiletransfer_ip=\nquery_port=${SERVER_PORT}\nquery_ip=0.0.0.0\nquery_ip_whitelist=query_ip_whitelist.txt\nquery_ip_blacklist=query_ip_blacklist.txt\ndbplugin=ts3db_sqlite3\ndbpluginparameter=\ndbsqlpath=sql\/\ndbsqlcreatepath=create_sqlite\/\ndbconnections=10\nlogpath=logs\nlogquerycommands=0\ndbclientkeepdays=30\nlogappend=0\nquery_skipbruteforcecheck=0\" > \/mnt\/server\/ts3server.ini", + "script": "#!\/bin\/ash\n# TS3 Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add tar curl\n\ncd \/tmp\n\ncurl -sSLO http:\/\/dl.4players.de\/ts\/releases\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\n\ntar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\ncp -r teamspeak3-server_linux_amd64\/* \/mnt\/server\n\necho \"machine_id=\ndefault_voice_port=${SERVER_PORT}\nvoice_ip=0.0.0.0\nlicensepath=\nfiletransfer_port=30033\nfiletransfer_ip=\nquery_port=${SERVER_PORT}\nquery_ip=0.0.0.0\nquery_ip_whitelist=query_ip_whitelist.txt\nquery_ip_blacklist=query_ip_blacklist.txt\ndbplugin=ts3db_sqlite3\ndbpluginparameter=\ndbsqlpath=sql\/\ndbsqlcreatepath=create_sqlite\/\ndbconnections=10\nlogpath=logs\nlogquerycommands=0\ndbclientkeepdays=30\nlogappend=0\nquery_skipbruteforcecheck=0\" > \/mnt\/server\/ts3server.ini\n\ntouch \/mnt\/server\/.ts3server_license_accepted", "container": "alpine:3.4", "entrypoint": "ash" } @@ -27,10 +27,10 @@ "name": "Server Version", "description": "The version of Teamspeak 3 to use when running the server.", "env_variable": "TS_VERSION", - "default_value": "3.0.13.8", + "default_value": "3.1.1", "user_viewable": 1, "user_editable": 1, "rules": "required|regex:\/^([0-9_\\.-]{5,10})$\/" } ] -} \ No newline at end of file +} diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index 58003a338..a2ad69054 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -145,6 +145,18 @@ p.small { font-size: 14px !important; } +.table .min-size { + width:1px; + white-space: nowrap; +} + +@media (max-width:767px) { + .box-header > .box-tools { + position: relative !important; + padding: 0px 10px 10px; + } +} + .middle, .align-middle { vertical-align: middle !important; } diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js index 9713f6587..2ac6b8bbc 100644 --- a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js +++ b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js @@ -1,4 +1,4 @@ -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+delName+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';$.each(selectedItems,function(key,value){formattedItems+=''+value+', '});formattedItems=formattedItems.slice(0,-2);swal({type:'warning',title:'',text:'Are you sure you want to delete:'+formattedItems+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}}]);return ActionsClass}(); +'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+delName+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';var i=0;$.each(selectedItems,function(key,value){formattedItems+=''+value+', ';i++;return i<5});formattedItems=formattedItems.slice(0,-2);if(selectedItems.length>5){formattedItems+=', and '+(selectedItems.length-5)+' other(s)'}swal({type:'warning',title:'',text:'Are you sure you want to delete the following files: '+formattedItems+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}}]);return ActionsClass}(); 'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i New File
  • New Folder
  • '}if(Pterodactyl.permissions.downloadFiles||Pterodactyl.permissions.deleteFiles){buildMenu+='
  • '}if(Pterodactyl.permissions.downloadFiles){buildMenu+=''}if(Pterodactyl.permissions.deleteFiles){buildMenu+='
  • Delete
  • '}buildMenu+='';return buildMenu}},{key:'rightClick',value:function rightClick(){var _this=this;$('[data-action="toggleMenu"]').on('mousedown',function(event){event.preventDefault();if($(document).find('#fileOptionMenu').is(':visible')){$('body').trigger('click');return}_this.showMenu(event)});$('#file_listing > tbody td').on('contextmenu',function(event){_this.showMenu(event)})}},{key:'showMenu',value:function showMenu(event){var _this2=this;var parent=$(event.target).closest('tr');var menu=$(this.makeMenu(parent));if(parent.data('type')==='disabled')return;event.preventDefault();$(menu).appendTo('body');$(menu).data('invokedOn',$(event.target)).show().css({position:'absolute',left:event.pageX-150,top:event.pageY});this.activeLine=parent;this.activeLine.addClass('active');var Actions=new ActionsClass(parent,menu);if(Pterodactyl.permissions.moveFiles){$(menu).find('li[data-action="move"]').unbind().on('click',function(e){e.preventDefault();Actions.move()});$(menu).find('li[data-action="rename"]').unbind().on('click',function(e){e.preventDefault();Actions.rename()})}if(Pterodactyl.permissions.copyFiles){$(menu).find('li[data-action="copy"]').unbind().on('click',function(e){e.preventDefault();Actions.copy()})}if(Pterodactyl.permissions.compressFiles){if(parent.data('type')==='folder'){$(menu).find('li[data-action="compress"]').removeClass('hidden')}$(menu).find('li[data-action="compress"]').unbind().on('click',function(e){e.preventDefault();Actions.compress()})}if(Pterodactyl.permissions.decompressFiles){if(_.without(['application/zip','application/gzip','application/x-gzip'],parent.data('mime')).length<3){$(menu).find('li[data-action="decompress"]').removeClass('hidden')}$(menu).find('li[data-action="decompress"]').unbind().on('click',function(e){e.preventDefault();Actions.decompress()})}if(Pterodactyl.permissions.createFiles){$(menu).find('li[data-action="folder"]').unbind().on('click',function(e){e.preventDefault();Actions.folder()})}if(Pterodactyl.permissions.downloadFiles){if(parent.data('type')==='file'){$(menu).find('li[data-action="download"]').removeClass('hidden')}$(menu).find('li[data-action="download"]').unbind().on('click',function(e){e.preventDefault();Actions.download()})}if(Pterodactyl.permissions.deleteFiles){$(menu).find('li[data-action="delete"]').unbind().on('click',function(e){e.preventDefault();Actions.delete()})}$(window).unbind().on('click',function(event){if($(event.target).is('.disable-menu-hide')){event.preventDefault();return}$(menu).unbind().remove();if(!_.isNull(_this2.activeLine))_this2.activeLine.removeClass('active')})}},{key:'directoryClick',value:function directoryClick(){$('a[data-action="directory-view"]').on('click',function(event){event.preventDefault();var path=$(this).parent().data('path')||'';var name=$(this).parent().data('name')||'';window.location.hash=encodeURIComponent(path+name);Files.list()})}}]);return ContextMenuClass}();window.ContextMenu=new ContextMenuClass; 'use strict';var _typeof=typeof Symbol==='function'&&typeof Symbol.iterator==='symbol'?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype?'symbol':typeof obj};var _createClass=function(){function defineProperties(target,props){for(var i=0;i' + delName + '? There is no reversing this action.', + text: 'Are you sure you want to delete ' + delName + '?', html: true, showCancelButton: true, showConfirmButton: true, @@ -380,16 +380,22 @@ class ActionsClass { if (selectedItems.length != 0) { let formattedItems = ""; + let i = 0; $.each(selectedItems, function(key, value) { - formattedItems += ("" + value + ", "); - }) + formattedItems += ("" + value + ", "); + i++; + return i < 5; + }); formattedItems = formattedItems.slice(0, -2); + if (selectedItems.length > 5) { + formattedItems += ', and ' + (selectedItems.length - 5) + ' other(s)'; + } swal({ type: 'warning', title: '', - text: 'Are you sure you want to delete:' + formattedItems + '? There is no reversing this action.', + text: 'Are you sure you want to delete the following files: ' + formattedItems + '?', html: true, showCancelButton: true, showConfirmButton: true, diff --git a/public/themes/pterodactyl/js/frontend/serverlist.js b/public/themes/pterodactyl/js/frontend/serverlist.js index 4b1d09197..1c6865a9b 100644 --- a/public/themes/pterodactyl/js/frontend/serverlist.js +++ b/public/themes/pterodactyl/js/frontend/serverlist.js @@ -80,8 +80,11 @@ } } }).fail(function (jqXHR) { - console.error(jqXHR); - element.find('[data-action="status"]').html('Error'); + if (jqXHR.status === 504) { + element.find('[data-action="status"]').html('Gateway Timeout'); + } else { + element.find('[data-action="status"]').html('Error'); + } }); }).promise().done(function () { setTimeout(updateServerStatus, 10000); diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php index 4a5250327..1fd706a8b 100644 --- a/resources/lang/en/command/messages.php +++ b/resources/lang/en/command/messages.php @@ -1,6 +1,11 @@ [ + 'warning' => 'It appears you have already configured an application encryption key. Continuing with this process with overwrite that key and cause data corruption for any existing encrypted data. DO NOT CONTINUE UNLESS YOU KNOW WHAT YOU ARE DOING.', + 'confirm' => 'I understand the consequences of performing this command and accept all responsibility for the loss of encrypted data.', + 'final_confirm' => 'Are you sure you wish to continue? Changing the application encryption key WILL CAUSE DATA LOSS.', + ], 'location' => [ 'no_location_found' => 'Could not locate a record matching the provided short code.', 'ask_short' => 'Location Short Code', diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index 73b910d0d..96395baed 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -8,9 +8,10 @@ return [ ], 'allocations' => [ 'server_using' => 'A server is currently assigned to this allocation. An allocation can only be deleted if no server is currently assigned.', - 'too_many_ports' => 'Adding more than 1000 ports at a single time is not supported. Please use a smaller range.', + 'too_many_ports' => 'Adding more than 1000 ports in a single range at once is not supported.', 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', + 'port_out_of_range' => 'Ports in an allocation must be greater than 1024 and less than or equal to 65535.', ], 'nest' => [ 'delete_has_servers' => 'A Nest with active servers attached to it cannot be deleted from the Panel.', diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php index f8a9deebb..c97c18a78 100644 --- a/resources/lang/en/navigation.php +++ b/resources/lang/en/navigation.php @@ -27,5 +27,6 @@ return [ 'edit_file' => 'Edit File', 'admin_header' => 'ADMINISTRATIVE', 'admin' => 'Server Configuration', + 'server_name' => 'Server Name', ], ]; diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index 8941c4793..b84f76387 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -273,8 +273,8 @@ return [ 'last_modified' => 'Last Modified', 'add_new' => 'Add New File', 'add_folder' => 'Add New Folder', - 'mass_actions' => 'Mass actions', - 'delete' => 'Delete', + 'mass_actions' => 'Mass Actions', + 'delete' => 'Delete Files', 'edit' => [ 'header' => 'Edit File', 'header_sub' => 'Make modifications to a file from the web.', @@ -289,6 +289,11 @@ return [ ], ], 'config' => [ + 'name' => [ + 'header' => 'Server Name', + 'header_sub' => 'Change this server\'s name.', + 'details' => 'The server name is only a reference to this server on the panel, and will not affect any server specific configurations that may display to users in games.', + ], 'startup' => [ 'header' => 'Start Configuration', 'header_sub' => 'Control server startup arguments.', diff --git a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php index ab2ac46f7..60bc6d530 100644 --- a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php @@ -134,26 +134,11 @@ {!! Theme::js('vendor/lodash/lodash.js') !!} - @endsection diff --git a/resources/themes/pterodactyl/admin/settings/advanced.blade.php b/resources/themes/pterodactyl/admin/settings/advanced.blade.php index 5a7b4d724..7a1d616c5 100644 --- a/resources/themes/pterodactyl/admin/settings/advanced.blade.php +++ b/resources/themes/pterodactyl/admin/settings/advanced.blade.php @@ -34,6 +34,12 @@

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

    +
    + +
    + +
    +
    @@ -41,12 +47,6 @@

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

    -
    - -
    - -
    -
    @if($showRecaptchaWarning)
    diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php index aac3507c2..d67eb6b41 100644 --- a/resources/themes/pterodactyl/layouts/admin.blade.php +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -19,7 +19,7 @@ - + @include('layouts.scripts') diff --git a/resources/themes/pterodactyl/layouts/auth.blade.php b/resources/themes/pterodactyl/layouts/auth.blade.php index 33664d074..0157864eb 100644 --- a/resources/themes/pterodactyl/layouts/auth.blade.php +++ b/resources/themes/pterodactyl/layouts/auth.blade.php @@ -18,7 +18,7 @@ - + @section('scripts') {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} diff --git a/resources/themes/pterodactyl/layouts/error.blade.php b/resources/themes/pterodactyl/layouts/error.blade.php index f7c60b1bf..0ec1b00fa 100644 --- a/resources/themes/pterodactyl/layouts/error.blade.php +++ b/resources/themes/pterodactyl/layouts/error.blade.php @@ -19,7 +19,7 @@ - + @section('scripts') {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php index 060c76ced..020f8b637 100644 --- a/resources/themes/pterodactyl/layouts/master.blade.php +++ b/resources/themes/pterodactyl/layouts/master.blade.php @@ -19,7 +19,7 @@ - + @include('layouts.scripts') @@ -170,7 +170,7 @@ @endcan - @if(Gate::allows('view-startup', $server) || Gate::allows('access-sftp', $server) || Gate::allows('view-allocation', $server)) + @if(Gate::allows('view-startup', $server) || Gate::allows('access-sftp', $server) || Gate::allows('view-allocations', $server))
  • - @can('view-allocation', $server) + @can('view-name', $server) +
  • @lang('navigation.server.server_name')
  • + @endcan + @can('view-allocations', $server)
  • @lang('navigation.server.port_allocations')
  • @endcan @can('access-sftp', $server) diff --git a/resources/themes/pterodactyl/server/files/list.blade.php b/resources/themes/pterodactyl/server/files/list.blade.php index bbe2b24d8..e4e88b606 100644 --- a/resources/themes/pterodactyl/server/files/list.blade.php +++ b/resources/themes/pterodactyl/server/files/list.blade.php @@ -6,45 +6,45 @@

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

    -
    -
    - - -
    - +
    + +
    - +
    - - - - - + + + + @if (isset($directory['first']) && $directory['first'] === true) - + @@ -53,7 +53,7 @@ @endif @if (isset($directory['show']) && $directory['show'] === true) - + @@ -64,7 +64,9 @@ @endif @foreach ($folders as $folder) - + @@ -79,12 +81,14 @@ {{ $carbon->diffForHumans() }} @endif - + @endforeach @foreach ($files as $file) - - + @endforeach diff --git a/resources/themes/pterodactyl/server/settings/name.blade.php b/resources/themes/pterodactyl/server/settings/name.blade.php new file mode 100644 index 000000000..6d59162d0 --- /dev/null +++ b/resources/themes/pterodactyl/server/settings/name.blade.php @@ -0,0 +1,50 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} +@extends('layouts.master') + +@section('title') + @lang('server.config.name.header') +@endsection + +@section('content-header') +

    @lang('server.config.name.header')@lang('server.config.name.header_sub')

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

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

    +
    +
    +
    + +
    + +
    +
    +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/frontend/server.socket.js') !!} +@endsection diff --git a/routes/server.php b/routes/server.php index a05f4b00b..f5d06b7e3 100644 --- a/routes/server.php +++ b/routes/server.php @@ -19,11 +19,12 @@ Route::get('/console', 'ConsoleController@console')->name('server.console'); */ Route::group(['prefix' => 'settings'], function () { Route::get('/allocation', 'Settings\AllocationController@index')->name('server.settings.allocation'); - Route::patch('/allocation', 'Settings\AllocationController@update'); - + Route::get('/name', 'Settings\NameController@index')->name('server.settings.name'); Route::get('/sftp', 'Settings\SftpController@index')->name('server.settings.sftp'); - Route::get('/startup', 'Settings\StartupController@index')->name('server.settings.startup'); + + Route::patch('/allocation', 'Settings\AllocationController@update'); + Route::patch('/name', 'Settings\NameController@update'); Route::patch('/startup', 'Settings\StartupController@update'); }); diff --git a/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php b/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php index 0f33f6735..36f1bc0b8 100644 --- a/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php +++ b/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php @@ -37,8 +37,8 @@ class SetSessionDriverTest extends MiddlewareTestCase */ public function testProductionEnvironment() { - $this->appMock->shouldReceive('environment')->withNoArgs()->once()->andReturn('production'); - $this->config->shouldReceive('set')->with('session.driver', 'array')->once()->andReturnNull(); + $this->config->shouldReceive('get')->once()->with('app.debug')->andReturn(false); + $this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull(); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } @@ -48,11 +48,10 @@ class SetSessionDriverTest extends MiddlewareTestCase */ public function testLocalEnvironment() { - $this->appMock->shouldReceive('environment')->withNoArgs()->once()->andReturn('local'); - $this->appMock->shouldReceive('make')->with(LaravelDebugbar::class)->once()->andReturnSelf(); - $this->appMock->shouldReceive('disable')->withNoArgs()->once()->andReturnNull(); - - $this->config->shouldReceive('set')->with('session.driver', 'array')->once()->andReturnNull(); + $this->config->shouldReceive('get')->once()->with('app.debug')->andReturn(true); + $this->appMock->shouldReceive('make')->once()->with(LaravelDebugbar::class)->andReturnSelf(); + $this->appMock->shouldReceive('disable')->once()->withNoArgs()->andReturnNull(); + $this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull(); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } diff --git a/tests/Unit/Services/Allocations/AssignmentServiceTest.php b/tests/Unit/Services/Allocations/AssignmentServiceTest.php index 805b789a3..7a4eb067c 100644 --- a/tests/Unit/Services/Allocations/AssignmentServiceTest.php +++ b/tests/Unit/Services/Allocations/AssignmentServiceTest.php @@ -1,30 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Tests\Unit\Services\Allocations; use Exception; use Mockery as m; use Tests\TestCase; -use phpmock\phpunit\PHPMock; use Pterodactyl\Models\Node; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class AssignmentServiceTest extends TestCase { - use PHPMock; - /** - * @var \Illuminate\Database\ConnectionInterface + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock */ protected $connection; @@ -34,15 +23,10 @@ class AssignmentServiceTest extends TestCase protected $node; /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock */ protected $repository; - /** - * @var \Pterodactyl\Services\Allocations\AssignmentService - */ - protected $service; - /** * Setup tests. */ @@ -50,19 +34,9 @@ class AssignmentServiceTest extends TestCase { parent::setUp(); - // Due to a bug in PHP, this is necessary since we only have a single test - // that relies on this mock. If this does not exist the test will fail to register - // correctly. - // - // This can also be avoided if tests were run in isolated processes, or if that test - // came first, but neither of those are good solutions, so this is the next best option. - PHPMock::defineFunctionMock('\\Pterodactyl\\Services\\Allocations', 'gethostbyname'); - $this->node = factory(Node::class)->make(); $this->connection = m::mock(ConnectionInterface::class); $this->repository = m::mock(AllocationRepositoryInterface::class); - - $this->service = new AssignmentService($this->repository, $this->connection); } /** @@ -72,22 +46,22 @@ class AssignmentServiceTest extends TestCase { $data = [ 'allocation_ip' => '192.168.1.1', - 'allocation_ports' => ['1024'], + 'allocation_ports' => ['2222'], ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); $this->repository->shouldReceive('insertIgnore')->with([ [ 'node_id' => $this->node->id, 'ip' => '192.168.1.1', - 'port' => 1024, + 'port' => 2222, 'ip_alias' => null, 'server_id' => null, ], ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); - $this->service->handle($this->node->id, $data); + $this->getService()->handle($this->node, $data); } /** @@ -97,18 +71,11 @@ class AssignmentServiceTest extends TestCase { $data = [ 'allocation_ip' => '192.168.1.1', - 'allocation_ports' => ['1024-1026'], + 'allocation_ports' => ['1025-1027'], ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('insertIgnore')->with([ - [ - 'node_id' => $this->node->id, - 'ip' => '192.168.1.1', - 'port' => 1024, - 'ip_alias' => null, - 'server_id' => null, - ], + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->once()->with([ [ 'node_id' => $this->node->id, 'ip' => '192.168.1.1', @@ -123,10 +90,17 @@ class AssignmentServiceTest extends TestCase 'ip_alias' => null, 'server_id' => null, ], - ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + [ + 'node_id' => $this->node->id, + 'ip' => '192.168.1.1', + 'port' => 1027, + 'ip_alias' => null, + 'server_id' => null, + ], + ])->andReturn(true); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); - $this->service->handle($this->node->id, $data); + $this->getService()->handle($this->node, $data); } /** @@ -136,23 +110,23 @@ class AssignmentServiceTest extends TestCase { $data = [ 'allocation_ip' => '192.168.1.1', - 'allocation_ports' => ['1024'], + 'allocation_ports' => ['2222'], 'allocation_alias' => 'my.alias.net', ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('insertIgnore')->with([ + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->once()->with([ [ 'node_id' => $this->node->id, 'ip' => '192.168.1.1', - 'port' => 1024, + 'port' => 2222, 'ip_alias' => 'my.alias.net', 'server_id' => null, ], - ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + ])->andReturn(true); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); - $this->service->handle($this->node->id, $data); + $this->getService()->handle($this->node, $data); } /** @@ -161,26 +135,23 @@ class AssignmentServiceTest extends TestCase public function testDomainNamePassedInPlaceOfIPAddress() { $data = [ - 'allocation_ip' => 'test-domain.com', - 'allocation_ports' => ['1024'], + 'allocation_ip' => 'unit-test-static.pterodactyl.io', + 'allocation_ports' => ['2222'], ]; - $this->getFunctionMock('\\Pterodactyl\\Services\\Allocations', 'gethostbyname') - ->expects($this->once())->willReturn('192.168.1.1'); - - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('insertIgnore')->with([ + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->once()->with([ [ 'node_id' => $this->node->id, - 'ip' => '192.168.1.1', - 'port' => 1024, + 'ip' => '127.0.0.1', + 'port' => 2222, 'ip_alias' => null, 'server_id' => null, ], - ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + ])->andReturn(true); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); - $this->service->handle($this->node->id, $data); + $this->getService()->handle($this->node, $data); } /** @@ -190,54 +161,55 @@ class AssignmentServiceTest extends TestCase { $data = [ 'allocation_ip' => '192.168.1.100/31', - 'allocation_ports' => ['1024'], + 'allocation_ports' => ['2222'], ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('insertIgnore')->with([ + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->repository->shouldReceive('insertIgnore')->once()->with([ [ 'node_id' => $this->node->id, 'ip' => '192.168.1.100', - 'port' => 1024, + 'port' => 2222, 'ip_alias' => null, 'server_id' => null, ], - ])->once()->andReturn(true); + ])->andReturn(true); - $this->repository->shouldReceive('insertIgnore')->with([ + $this->repository->shouldReceive('insertIgnore')->once()->with([ [ 'node_id' => $this->node->id, 'ip' => '192.168.1.101', - 'port' => 1024, + 'port' => 2222, 'ip_alias' => null, 'server_id' => null, ], - ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + ])->andReturn(true); + $this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull(); - $this->service->handle($this->node->id, $data); + $this->getService()->handle($this->node, $data); } /** * Test that a CIDR IP address with a range works properly. + * + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException + * @expectedExceptionMessage CIDR notation only allows masks between /25 and /32. */ public function testCIDRNotatedIPAddressOutsideRangeLimit() { $data = [ 'allocation_ip' => '192.168.1.100/20', - 'allocation_ports' => ['1024'], + 'allocation_ports' => ['2222'], ]; - try { - $this->service->handle($this->node->id, $data); - } catch (Exception $exception) { - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('exceptions.allocations.cidr_out_of_range'), $exception->getMessage()); - } + $this->getService()->handle($this->node, $data); } /** * Test that an exception is thrown if there are too many ports. + * + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException + * @expectedExceptionMessage Adding more than 1000 ports in a single range at once is not supported. */ public function testAllocationWithPortsExceedingLimit() { @@ -246,22 +218,16 @@ class AssignmentServiceTest extends TestCase 'allocation_ports' => ['5000-7000'], ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); - try { - $this->service->handle($this->node->id, $data); - } catch (Exception $exception) { - if (! $exception instanceof DisplayException) { - throw $exception; - } - - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('exceptions.allocations.too_many_ports'), $exception->getMessage()); - } + $this->getService()->handle($this->node, $data); } /** * Test that an exception is thrown if an invalid port is provided. + * + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException + * @expectedExceptionMessage The mapping provided for test123 was invalid and could not be processed. */ public function testInvalidPortProvided() { @@ -270,42 +236,52 @@ class AssignmentServiceTest extends TestCase 'allocation_ports' => ['test123'], ]; - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - - try { - $this->service->handle($this->node->id, $data); - } catch (Exception $exception) { - if (! $exception instanceof DisplayException) { - throw $exception; - } - - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('exceptions.allocations.invalid_mapping', ['port' => 'test123']), $exception->getMessage()); - } + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->getService()->handle($this->node, $data); } /** - * Test that a model can be passed in place of an ID. + * Test that ports outside of defined limits throw an error. + * + * @param array $ports + * + * @dataProvider invalidPortsDataProvider + * @expectedException \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException + * @expectedExceptionMessage Ports in an allocation must be greater than 1024 and less than or equal to 65535. */ - public function testModelCanBePassedInPlaceOfNodeModel() + public function testPortRangeOutsideOfRangeLimits(array $ports) { - $data = [ - 'allocation_ip' => '192.168.1.1', - 'allocation_ports' => ['1024'], + $data = ['allocation_ip' => '192.168.1.1', 'allocation_ports' => $ports]; + + $this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull(); + $this->getService()->handle($this->node, $data); + } + + /** + * Provide ports and ranges of ports that exceed the viable port limits for the software. + * + * @return array + */ + public function invalidPortsDataProvider(): array + { + return [ + [['65536']], + [['1024']], + [['1000']], + [['0']], + [['65530-65540']], + [['65540-65560']], + [[PHP_INT_MAX]], ]; + } - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('insertIgnore')->with([ - [ - 'node_id' => $this->node->id, - 'ip' => '192.168.1.1', - 'port' => 1024, - 'ip_alias' => null, - 'server_id' => null, - ], - ])->once()->andReturn(true); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $this->service->handle($this->node, $data); + /** + * Returns an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Allocations\AssignmentService + */ + private function getService(): AssignmentService + { + return new AssignmentService($this->repository, $this->connection); } } diff --git a/tests/Unit/Services/Servers/StartupModificationServiceTest.php b/tests/Unit/Services/Servers/StartupModificationServiceTest.php index 99453e515..5b6046753 100644 --- a/tests/Unit/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Unit/Services/Servers/StartupModificationServiceTest.php @@ -121,14 +121,18 @@ class StartupModificationServiceTest extends TestCase $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_ADMIN)->once()->andReturnNull(); $this->validatorService->shouldReceive('handle')->with(456, ['test' => 'abcd1234'])->once()->andReturn( - collect([(object) ['id' => 1, 'value' => 'stored-value']]) + collect([(object) ['id' => 1, 'value' => 'stored-value'], (object) ['id' => 2, 'value' => null]]) ); - $this->serverVariableRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); - $this->serverVariableRepository->shouldReceive('updateOrCreate')->with([ + $this->serverVariableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->once()->with([ 'server_id' => $model->id, 'variable_id' => 1, - ], ['variable_value' => 'stored-value'])->once()->andReturnNull(); + ], ['variable_value' => 'stored-value'])->andReturnNull(); + + $this->serverVariableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->once()->with([ + 'server_id' => $model->id, + 'variable_id' => 2, + ], ['variable_value' => ''])->andReturnNull(); $this->eggRepository->shouldReceive('setColumns->find')->once()->with($eggModel->id)->andReturn($eggModel);
    - + + @lang('server.files.file_name')@lang('server.files.file_name')
    ← {{ $directory['link_show'] }}
    + + {{ $folder['entry'] }} + +
    + {{-- oh boy --}} @if(in_array($file['mime'], [ 'application/x-7z-compressed', @@ -156,7 +160,9 @@ {{ $carbon->diffForHumans() }} @endif + +