Merge branch 'release/v0.7.6'

This commit is contained in:
Dane Everitt 2018-03-10 15:23:20 -06:00
commit 41e27a6612
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
48 changed files with 638 additions and 287 deletions

View file

@ -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.

10
Vagrantfile vendored
View file

@ -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

View file

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Console\Commands\Overrides;
use Illuminate\Foundation\Console\KeyGenerateCommand as BaseKeyGenerateCommand;
class KeyGenerateCommand extends BaseKeyGenerateCommand
{
/**
* Override the default Laravel key generation command to throw a warning to the user
* if it appears that they have already generated an application encryption key.
*/
public function handle()
{
if (! empty(config('app.key')) && $this->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();
}
}

View file

@ -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());
}
}

View file

@ -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.
*

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Exceptions\Service\Allocation;
use Pterodactyl\Exceptions\DisplayException;
class CidrOutOfRangeException extends DisplayException
{
/**
* CidrOutOfRangeException constructor.
*/
public function __construct()
{
parent::__construct(trans('exceptions.allocations.cidr_out_of_range'));
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Pterodactyl\Exceptions\Service\Allocation;
use Pterodactyl\Exceptions\DisplayException;
class InvalidPortMappingException extends DisplayException
{
/**
* InvalidPortMappingException constructor.
*
* @param mixed $port
*/
public function __construct($port)
{
parent::__construct(trans('exceptions.allocations.invalid_mapping', ['port' => $port]));
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Exceptions\Service\Allocation;
use Pterodactyl\Exceptions\DisplayException;
class PortOutOfRangeException extends DisplayException
{
/**
* PortOutOfRangeException constructor.
*/
public function __construct()
{
parent::__construct(trans('exceptions.allocations.port_out_of_range'));
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Exceptions\Service\Allocation;
use Pterodactyl\Exceptions\DisplayException;
class TooManyPortsInRangeException extends DisplayException
{
/**
* TooManyPortsInRangeException constructor.
*/
public function __construct()
{
parent::__construct(trans('exceptions.allocations.too_many_ports'));
}
}

View file

@ -331,7 +331,10 @@ class NodesController extends Controller
* @param int|\Pterodactyl\Models\Node $node
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
*/
public function createAllocation(AllocationFormRequest $request, Node $node)
{

View file

@ -73,7 +73,10 @@ class AllocationController extends ApplicationApiController
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
*/
public function store(StoreAllocationRequest $request): array
{

View file

@ -4,6 +4,8 @@ namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Http\Response;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Http\Controllers\Controller;
use Symfony\Component\HttpKernel\Exception\HttpException;
@ -81,6 +83,8 @@ class IndexController extends Controller
try {
$response = $this->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());
}

View file

@ -0,0 +1,59 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Settings;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Http\Requests\Server\Settings\ChangeServerNameRequest;
class NameController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* NameController constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(ServerRepositoryInterface $repository)
{
$this->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);
}
}

View file

@ -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');

View file

@ -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();
}

View file

@ -0,0 +1,31 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Settings;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class ChangeServerNameRequest extends ServerFormRequest
{
/**
* Permission to use when checking if a user can access this resource.
*
* @return string
*/
protected function permission(): string
{
return 'edit-name';
}
/**
* Rules to use when validating the submitted data.
*
* @return array
*/
public function rules()
{
return [
'name' => Server::getCreateRules()['name'],
];
}
}

View file

@ -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;
}
/**

View file

@ -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,
],
],

View file

@ -1,26 +1,24 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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'),

View file

@ -105,7 +105,7 @@ class StartupModificationService
'server_id' => $server->id,
'variable_id' => $result->id,
], [
'variable_value' => $result->value,
'variable_value' => $result->value ?? '',
]);
});
}

View file

@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
@ -13,7 +15,7 @@
|
*/
require __DIR__.'/bootstrap/autoload.php';
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
@ -40,7 +42,7 @@ $status = $kernel->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.
|

View file

@ -1,17 +0,0 @@
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Composer Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__ . '/../vendor/autoload.php';

View file

@ -9,7 +9,7 @@ return [
| change this value if you are not maintaining your own internal versions.
*/
'version' => '0.7.5',
'version' => '0.7.6',
/*
|--------------------------------------------------------------------------

View file

@ -70,6 +70,12 @@ return [
'driver' => 'redis',
'connection' => 'default',
],
'sessions' => [
'driver' => env('SESSION_DRIVER', 'database'),
'table' => 'sessions',
'connection' => env('SESSION_DRIVER') === 'redis' ? 'sessions' : null,
],
],
/*

View file

@ -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),
],
],
];

View file

@ -69,7 +69,7 @@ return [
|
*/
'connection' => null,
'connection' => env('SESSION_DRIVER') === 'redis' ? 'sessions' : null,
/*
|--------------------------------------------------------------------------

View file

@ -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)$\/"
}
]
}
}

View file

@ -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})$\/"
}
]
}
}

View file

@ -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;
}

File diff suppressed because one or more lines are too long

View file

@ -284,7 +284,7 @@ class ActionsClass {
swal({
type: 'warning',
title: '',
text: 'Are you sure you want to delete <code>' + delName + '</code>? There is <strong>no</strong> reversing this action.',
text: 'Are you sure you want to delete <code>' + delName + '</code>?',
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 += ("<code>" + value + "</code>, ");
})
formattedItems += ("<code>" + value + "</code>, ");
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 <strong>no</strong> reversing this action.',
text: 'Are you sure you want to delete the following files: ' + formattedItems + '?',
html: true,
showCancelButton: true,
showConfirmButton: true,

View file

@ -80,8 +80,11 @@
}
}
}).fail(function (jqXHR) {
console.error(jqXHR);
element.find('[data-action="status"]').html('<span class="label label-default">Error</span>');
if (jqXHR.status === 504) {
element.find('[data-action="status"]').html('<span class="label label-default">Gateway Timeout</span>');
} else {
element.find('[data-action="status"]').html('<span class="label label-default">Error</span>');
}
});
}).promise().done(function () {
setTimeout(updateServerStatus, 10000);

View file

@ -1,6 +1,11 @@
<?php
return [
'key' => [
'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',

View file

@ -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.',

View file

@ -27,5 +27,6 @@ return [
'edit_file' => 'Edit File',
'admin_header' => 'ADMINISTRATIVE',
'admin' => 'Server Configuration',
'server_name' => 'Server Name',
],
];

View file

@ -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.',

View file

@ -134,26 +134,11 @@
{!! Theme::js('vendor/lodash/lodash.js') !!}
<script>
$(document).ready(function () {
$('#pNestId').select2({placeholder: 'Select a Nest'}).change();
$('#pEggId').select2({placeholder: 'Select a Nest Egg'});
$('#pPackId').select2({placeholder: 'Select a Service Pack'});
});
</script>
<script>
$('#pNestId').on('change', function (event) {
$('#pEggId').html('').select2({
data: $.map(_.get(Pterodactyl.nests, $(this).val() + '.eggs', []), function (item) {
return {
id: item.id,
text: item.name,
};
}),
}).val(Pterodactyl.server.egg_id).change();
});
$('#pEggId').on('change', function (event) {
var parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null);
var objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null);
$('#pEggId').select2({placeholder: 'Select a Nest Egg'}).on('change', function () {
var selectedEgg = _.isNull($(this).val()) ? $(this).find('option').first().val() : $(this).val();
var parentChain = _.get(Pterodactyl.nests, $("#pNestId").val());
var objectChain = _.get(parentChain, 'eggs.' + selectedEgg);
$('#setDefaultImage').html(_.get(objectChain, 'docker_image', 'undefined'));
$('#pDockerImage').val(_.get(objectChain, 'docker_image', 'undefined'));
@ -168,7 +153,7 @@
}
$('#pPackId').html('').select2({
data: [{ id: '0', text: 'No Service Pack' }].concat(
data: [{id: '0', text: 'No Service Pack'}].concat(
$.map(_.get(objectChain, 'packs', []), function (item, i) {
return {
id: item.id,
@ -202,9 +187,26 @@
</div> \
</div> \
</div>';
$('#appendVariablesTo').append(dataAppend);
$('#appendVariablesTo').find('#egg_variable_' + item.env_variable).val(setValue);
$('#appendVariablesTo').append(dataAppend).find('#egg_variable_' + item.env_variable).val(setValue);
});
});
$('#pNestId').select2({placeholder: 'Select a Nest'}).on('change', function () {
$('#pEggId').html('').select2({
data: $.map(_.get(Pterodactyl.nests, $(this).val() + '.eggs', []), function (item) {
return {
id: item.id,
text: item.name,
};
}),
});
if (_.isObject(_.get(Pterodactyl.nests, $(this).val() + '.eggs.' + Pterodactyl.server.egg_id))) {
$('#pEggId').val(Pterodactyl.server.egg_id);
}
$('#pEggId').change();
}).change();
});
</script>
@endsection

View file

@ -34,6 +34,12 @@
<p class="text-muted small">If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.</p>
</div>
</div>
<div class="form-group col-md-4">
<label class="control-label">Site Key</label>
<div>
<input type="text" required class="form-control" name="recaptcha:website_key" value="{{ old('recaptcha:website_key', config('recaptcha.website_key')) }}">
</div>
</div>
<div class="form-group col-md-4">
<label class="control-label">Secret Key</label>
<div>
@ -41,12 +47,6 @@
<p class="text-muted small">Used for communication between your site and Google. Be sure to keep it a secret.</p>
</div>
</div>
<div class="form-group col-md-4">
<label class="control-label">Website Key</label>
<div>
<input type="text" required class="form-control" name="recaptcha:website_key" value="{{ old('recaptcha:website_key', config('recaptcha.website_key')) }}">
</div>
</div>
</div>
@if($showRecaptchaWarning)
<div class="row">

View file

@ -19,7 +19,7 @@
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#bc6e3c">
<link rel="shortcut icon" href="/favicons/favicon.ico">
<meta name="msapplication-config" content="/favicons/browserconfig.xml">
<meta name="theme-color" content="#367fa9">
<meta name="theme-color" content="#0e4688">
@include('layouts.scripts')

View file

@ -18,7 +18,7 @@
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#bc6e3c">
<link rel="shortcut icon" href="/favicons/favicon.ico">
<meta name="msapplication-config" content="/favicons/browserconfig.xml">
<meta name="theme-color" content="#367fa9">
<meta name="theme-color" content="#0e4688">
@section('scripts')
{!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!}

View file

@ -19,7 +19,7 @@
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#bc6e3c">
<link rel="shortcut icon" href="/favicons/favicon.ico">
<meta name="msapplication-config" content="/favicons/browserconfig.xml">
<meta name="theme-color" content="#367fa9">
<meta name="theme-color" content="#0e4688">
@section('scripts')
{!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!}

View file

@ -19,7 +19,7 @@
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#bc6e3c">
<link rel="shortcut icon" href="/favicons/favicon.ico">
<meta name="msapplication-config" content="/favicons/browserconfig.xml">
<meta name="theme-color" content="#367fa9">
<meta name="theme-color" content="#0e4688">
@include('layouts.scripts')
@ -170,7 +170,7 @@
</a>
</li>
@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))
<li class="treeview
@if(starts_with(Route::currentRouteName(), 'server.settings'))
active
@ -184,7 +184,10 @@
</span>
</a>
<ul class="treeview-menu">
@can('view-allocation', $server)
@can('view-name', $server)
<li class="{{ Route::currentRouteName() !== 'server.settings.name' ?: 'active' }}"><a href="{{ route('server.settings.name', $server->uuidShort) }}"><i class="fa fa-angle-right"></i> @lang('navigation.server.server_name')</a></li>
@endcan
@can('view-allocations', $server)
<li class="{{ Route::currentRouteName() !== 'server.settings.allocation' ?: 'active' }}"><a href="{{ route('server.settings.allocation', $server->uuidShort) }}"><i class="fa fa-angle-right"></i> @lang('navigation.server.port_allocations')</a></li>
@endcan
@can('access-sftp', $server)

View file

@ -6,45 +6,45 @@
<div class="box-header with-border">
<h3 class="box-title">/home/container{{ $directory['header'] }}</h3>
<div class="box-tools pull-right">
<div class="btn-group">
<button type="button" id="mass_actions" class="btn btn-sm btn-info dropdown-toggle disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@lang('server.files.mass_actions') <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-massactions">
<li><a href="#" id="selective-deletion" data-action="selective-deletion">@lang('server.files.delete') <i class="fa fa-fw fa-trash-o"></i></a></li>
</ul>
</div>
<button class="btn btn-sm btn-success btn-icon" data-action="add-folder">
<i class="fa fa-fw fa-folder-open-o"></i>
</button>
<div class="box-tools">
<a href="/server/{{ $server->uuidShort }}/files/add/@if($directory['header'] !== '')?dir={{ $directory['header'] }}@endif">
<button class="btn btn-success btn-sm btn-icon">
<i class="fa fa-fw fa-file-text-o"></i>
New File <i class="fa fa-fw fa-file-text-o"></i>
</button>
</a>
<button class="btn btn-sm btn-success btn-icon" data-action="add-folder">
New Folder <i class="fa fa-fw fa-folder-open-o"></i>
</button>
<label class="btn btn-primary btn-sm btn-icon">
<i class="fa fa-fw fa-upload"></i><input type="file" id="files_touch_target" class="hidden">
Upload <i class="fa fa-fw fa-upload"></i><input type="file" id="files_touch_target" class="hidden">
</label>
<div class="btn-group hidden-xs">
<button type="button" id="mass_actions" class="btn btn-sm btn-default dropdown-toggle disabled" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@lang('server.files.mass_actions') <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-massactions">
<li><a href="#" id="selective-deletion" data-action="selective-deletion">@lang('server.files.delete') <i class="fa fa-fw fa-trash-o"></i></a></li>
</ul>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover" id="file_listing" data-current-dir="{{ $directory['header'] }}">
<table class="table table-hover" id="file_listing" data-current-dir="{{ rtrim($directory['header'], '/') . '/' }}">
<thead>
<tr>
<th style="width:4%;" class="middle">
<input type="checkbox" class="select-all-files" data-action="selectAll"><i class="fa fa-refresh muted muted-hover use-pointer" data-action="reload-files" style="font-size:14px;"></i>
<th class="middle min-size">
<input type="checkbox" class="select-all-files hidden-xs" data-action="selectAll"><i class="fa fa-refresh muted muted-hover use-pointer" data-action="reload-files" style="font-size:14px;"></i>
</th>
<th style="width:55%">@lang('server.files.file_name')</th>
<th style="width:15%" class="hidden-xs">@lang('server.files.size')</th>
<th style="width:20%" class="hidden-xs">@lang('server.files.last_modified')</th>
<th style="width:6%"></th>
<th>@lang('server.files.file_name')</th>
<th class="hidden-xs">@lang('server.files.size')</th>
<th class="hidden-xs">@lang('server.files.last_modified')</th>
<th></th>
</tr>
</thead>
<tbody id="append_files_to">
@if (isset($directory['first']) && $directory['first'] === true)
<tr data-type="disabled">
<td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td class="middle min-size"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td><a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr;</a></a></td>
<td class="hidden-xs"></td>
<td class="hidden-xs"></td>
@ -53,7 +53,7 @@
@endif
@if (isset($directory['show']) && $directory['show'] === true)
<tr data-type="disabled">
<td><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td class="middle min-size"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td data-name="{{ rawurlencode($directory['link']) }}">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">&larr; {{ $directory['link_show'] }}</a>
</td>
@ -64,7 +64,9 @@
@endif
@foreach ($folders as $folder)
<tr data-type="folder">
<td data-identifier="type" class="middle"><input type="checkbox" class="select-folder" data-action="addSelection"><i class="fa fa-folder" style="margin-left: 0.859px;"></i></td>
<td class="middle min-size" data-identifier="type">
<input type="checkbox" class="select-folder hidden-xs" data-action="addSelection"><i class="fa fa-folder" style="margin-left: 0.859px;"></i>
</td>
<td data-identifier="name" data-name="{{ rawurlencode($folder['entry']) }}" data-path="@if($folder['directory'] !== ''){{ rawurlencode($folder['directory']) }}@endif/">
<a href="/server/{{ $server->uuidShort }}/files" data-action="directory-view">{{ $folder['entry'] }}</a>
</td>
@ -79,12 +81,14 @@
{{ $carbon->diffForHumans() }}
@endif
</td>
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></td>
<td class="min-size">
<button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></button>
</td>
</tr>
@endforeach
@foreach ($files as $file)
<tr data-type="file" data-mime="{{ $file['mime'] }}">
<td data-identifier="type" class="middle"><input type="checkbox" class="select-file" data-action="addSelection">
<td class="middle min-size" data-identifier="type"><input type="checkbox" class="select-file hidden-xs" data-action="addSelection">
{{-- oh boy --}}
@if(in_array($file['mime'], [
'application/x-7z-compressed',
@ -156,7 +160,9 @@
{{ $carbon->diffForHumans() }}
@endif
</td>
<td><button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></td>
<td class="min-size">
<button class="btn btn-xxs btn-default disable-menu-hide" data-action="toggleMenu" style="padding:2px 6px 0px;"><i class="fa fa-ellipsis-h disable-menu-hide"></i></button>
</td>
</tr>
@endforeach
</tbody>

View file

@ -0,0 +1,50 @@
{{-- Pterodactyl - Panel --}}
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
{{-- 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')
<h1>@lang('server.config.name.header')<small>@lang('server.config.name.header_sub')</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('index') }}">@lang('strings.home')</a></li>
<li><a href="{{ route('server.index', $server->uuidShort) }}">{{ $server->name }}</a></li>
<li>@lang('navigation.server.configuration')</li>
<li class="active">@lang('navigation.server.server_name')</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<form action="{{ route('server.settings.name', $server->uuidShort) }}" method="POST">
<div class="box">
<div class="box-body">
<div class="form-group no-margin-bottom">
<label class="control-label" for="pServerName">@lang('server.config.name.header')</label>
<div>
<input type="text" name="name" id="pServerName" class="form-control" value="{{ $server->name }}" />
<p class="small text-muted no-margin-bottom">@lang('server.config.name.details')</p>
</div>
</div>
</div>
<div class="box-footer">
{{ method_field('PATCH') }}
{{ csrf_field() }}
<input type="submit" class="btn btn-sm btn-primary pull-right" value="@lang('strings.submit')" />
</div>
</div>
</form>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
{!! Theme::js('js/frontend/server.socket.js') !!}
@endsection

View file

@ -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');
});

View file

@ -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());
}

View file

@ -1,30 +1,19 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);
}
}

View file

@ -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);