Merge branch 'develop' into hidedelifown
This commit is contained in:
commit
0ca13fc9d0
|
@ -3,6 +3,10 @@ This file is a running track of new features and fixes to each version of the pa
|
|||
|
||||
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||
|
||||
## v1.1.3
|
||||
### Fixed
|
||||
* Server bulk power actions command will no longer attempt to run commands against installing or suspended servers.
|
||||
|
||||
## v1.1.2
|
||||
### Fixed
|
||||
* Fixes an exception thrown while trying to validate IP access for the client API.
|
||||
|
|
|
@ -25,7 +25,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
||||
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
|
||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||
| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
||||
| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
||||
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
||||
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||
|
|
|
@ -2,30 +2,15 @@
|
|||
|
||||
namespace Pterodactyl\Console\Commands\Server;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class BulkPowerActionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository
|
||||
*/
|
||||
private $powerRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Validation\Factory
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -39,37 +24,20 @@ class BulkPowerActionCommand extends Command
|
|||
*/
|
||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||
|
||||
/**
|
||||
* BulkPowerActionCommand constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
||||
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
|
||||
* @param \Illuminate\Validation\Factory $validator
|
||||
*/
|
||||
public function __construct(
|
||||
DaemonPowerRepository $powerRepository,
|
||||
ServerRepositoryInterface $repository,
|
||||
ValidatorFactory $validator
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->validator = $validator;
|
||||
$this->powerRepository = $powerRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the bulk power request.
|
||||
*
|
||||
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
||||
* @param \Illuminate\Validation\Factory $validator
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator)
|
||||
{
|
||||
$action = $this->argument('action');
|
||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
|
||||
|
||||
$validator = $this->validator->make([
|
||||
$validator = $validator->make([
|
||||
'action' => $action,
|
||||
'nodes' => $nodes,
|
||||
'servers' => $servers,
|
||||
|
@ -89,23 +57,18 @@ class BulkPowerActionCommand extends Command
|
|||
throw new ValidationException($validator);
|
||||
}
|
||||
|
||||
$count = $this->repository->getServersForPowerActionCount($servers, $nodes);
|
||||
$count = $this->getQueryBuilder($servers, $nodes)->count();
|
||||
if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count])) && $this->input->isInteractive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
$servers = $this->repository->getServersForPowerAction($servers, $nodes);
|
||||
|
||||
$servers->each(function ($server) use ($action, &$bar) {
|
||||
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
|
||||
$bar->clear();
|
||||
|
||||
try {
|
||||
$this->powerRepository
|
||||
->setNode($server->node)
|
||||
->setServer($server)
|
||||
->send($action);
|
||||
} catch (RequestException $exception) {
|
||||
$powerRepository->setServer($server)->send($action);
|
||||
} catch (DaemonConnectionException $exception) {
|
||||
$this->output->error(trans('command/messages.server.power.action_failed', [
|
||||
'name' => $server->name,
|
||||
'id' => $server->id,
|
||||
|
@ -120,4 +83,28 @@ class BulkPowerActionCommand extends Command
|
|||
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query builder instance that will return the servers that should be affected.
|
||||
*
|
||||
* @param array $servers
|
||||
* @param array $nodes
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function getQueryBuilder(array $servers, array $nodes)
|
||||
{
|
||||
$instance = Server::query()
|
||||
->where('suspended', false)
|
||||
->where('installed', Server::STATUS_INSTALLED);
|
||||
|
||||
if (! empty($nodes) && ! empty($servers)) {
|
||||
$instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes);
|
||||
} else if (empty($nodes) && ! empty($servers)) {
|
||||
$instance->whereIn('id', $servers);
|
||||
} else if (! empty($nodes) && empty($servers)) {
|
||||
$instance->whereIn('node_id', $nodes);
|
||||
}
|
||||
|
||||
return $instance->with('node');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,25 +95,6 @@ interface ServerRepositoryInterface extends RepositoryInterface
|
|||
*/
|
||||
public function getByUuid(string $uuid): Server;
|
||||
|
||||
/**
|
||||
* Return all of the servers that should have a power action performed against them.
|
||||
*
|
||||
* @param int[] $servers
|
||||
* @param int[] $nodes
|
||||
* @param bool $returnCount
|
||||
* @return int|\Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false);
|
||||
|
||||
/**
|
||||
* Return the total number of servers that will be affected by the query.
|
||||
*
|
||||
* @param int[] $servers
|
||||
* @param int[] $nodes
|
||||
* @return int
|
||||
*/
|
||||
public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int;
|
||||
|
||||
/**
|
||||
* Check if a given UUID and UUID-Short string are unique to a server.
|
||||
*
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client\Account;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
|
||||
|
||||
|
@ -32,8 +31,8 @@ class UpdatePasswordRequest extends ClientApiRequest
|
|||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = User::getRulesForUpdate($this->user());
|
||||
|
||||
return ['password' => array_merge($rules['password'], ['confirmed'])];
|
||||
return [
|
||||
'password' => ['required', 'string', 'confirmed', 'min:8'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ class RunTaskJob extends Job implements ShouldQueue
|
|||
$commandRepository->setServer($server)->send($this->task->payload);
|
||||
break;
|
||||
case 'backup':
|
||||
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null);
|
||||
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
|
||||
|
|
|
@ -194,45 +194,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the servers that should have a power action performed against them.
|
||||
*
|
||||
* @param int[] $servers
|
||||
* @param int[] $nodes
|
||||
* @param bool $returnCount
|
||||
* @return int|\Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false)
|
||||
{
|
||||
$instance = $this->getBuilder();
|
||||
|
||||
if (! empty($nodes) && ! empty($servers)) {
|
||||
$instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes);
|
||||
} else if (empty($nodes) && ! empty($servers)) {
|
||||
$instance->whereIn('id', $servers);
|
||||
} else if (! empty($nodes) && empty($servers)) {
|
||||
$instance->whereIn('node_id', $nodes);
|
||||
}
|
||||
|
||||
if ($returnCount) {
|
||||
return $instance->count();
|
||||
}
|
||||
|
||||
return $instance->with('node')->cursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of servers that will be affected by the query.
|
||||
*
|
||||
* @param int[] $servers
|
||||
* @param int[] $nodes
|
||||
* @return int
|
||||
*/
|
||||
public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int
|
||||
{
|
||||
return $this->getServersForPowerAction($servers, $nodes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given UUID and UUID-Short string are unique to a server.
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
|||
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
|
||||
use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
use Pterodactyl\Services\Backups\DeleteBackupService;
|
||||
|
||||
class InitiateBackupService
|
||||
{
|
||||
|
@ -41,24 +42,32 @@ class InitiateBackupService
|
|||
*/
|
||||
private $backupManager;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Backups\DeleteBackupService
|
||||
*/
|
||||
private $deleteBackupService;
|
||||
|
||||
/**
|
||||
* InitiateBackupService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository
|
||||
* @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService
|
||||
* @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager
|
||||
*/
|
||||
public function __construct(
|
||||
BackupRepository $repository,
|
||||
ConnectionInterface $connection,
|
||||
DaemonBackupRepository $daemonBackupRepository,
|
||||
DeleteBackupService $deleteBackupService,
|
||||
BackupManager $backupManager
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->connection = $connection;
|
||||
$this->daemonBackupRepository = $daemonBackupRepository;
|
||||
$this->backupManager = $backupManager;
|
||||
$this->deleteBackupService = $deleteBackupService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,13 +105,8 @@ class InitiateBackupService
|
|||
* @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException
|
||||
*/
|
||||
public function handle(Server $server, string $name = null): Backup
|
||||
public function handle(Server $server, string $name = null, bool $override = false): Backup
|
||||
{
|
||||
// Do not allow the user to continue if this server is already at its limit.
|
||||
if (! $server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) {
|
||||
throw new TooManyBackupsException($server->backup_limit);
|
||||
}
|
||||
|
||||
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
|
||||
if ($previous->count() >= 2) {
|
||||
throw new TooManyRequestsHttpException(
|
||||
|
@ -111,6 +115,18 @@ class InitiateBackupService
|
|||
);
|
||||
}
|
||||
|
||||
// Check if the server has reached or exceeded it's backup limit
|
||||
if (!$server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) {
|
||||
// Do not allow the user to continue if this server is already at its limit and can't override.
|
||||
if (!$override || $server->backup_limit <= 0) {
|
||||
throw new TooManyBackupsException($server->backup_limit);
|
||||
}
|
||||
|
||||
// Remove oldest backup
|
||||
$oldestBackup = $server->backups()->where('is_successful', true)->orderByDesc('created_at')->first();
|
||||
$this->deleteBackupService->handle($oldestBackup);
|
||||
}
|
||||
|
||||
return $this->connection->transaction(function () use ($server, $name) {
|
||||
/** @var \Pterodactyl\Models\Backup $backup */
|
||||
$backup = $this->repository->create([
|
||||
|
|
|
@ -45,7 +45,7 @@ return [
|
|||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
'strict' => env('DB_STRICT_MODE', false),
|
||||
'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE')))
|
||||
'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC')))
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -68,7 +68,7 @@ return [
|
|||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => false,
|
||||
'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE')))
|
||||
'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC')))
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -83,10 +83,10 @@ class MergePermissionsTableIntoSubusers extends Migration
|
|||
->map(function ($value) {
|
||||
return self::$permissionsMap[$value] ?? null;
|
||||
})->filter(function ($value) {
|
||||
return !is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT;
|
||||
return ! is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT;
|
||||
})
|
||||
// All subusers get this permission, so make sure it gets pushed into the array.
|
||||
->merge([ Permission::ACTION_WEBSOCKET_CONNECT ])
|
||||
->merge([Permission::ACTION_WEBSOCKET_CONNECT])
|
||||
->unique()
|
||||
->values()
|
||||
->toJson();
|
||||
|
@ -103,12 +103,12 @@ class MergePermissionsTableIntoSubusers extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
$flipped = array_flip(self::$permissionsMap);
|
||||
$flipped = array_flip(array_filter(self::$permissionsMap));
|
||||
|
||||
foreach (DB::select('SELECT id, permissions FROM subusers') as $datum) {
|
||||
$values = [];
|
||||
foreach (json_decode($datum->permissions, true) as $permission) {
|
||||
if (!empty($v = $flipped[$permission])) {
|
||||
if (! empty($v = $flipped[$permission])) {
|
||||
$values[] = $datum->id;
|
||||
$values[] = $v;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"xterm-addon-fit": "^0.4.0",
|
||||
"xterm-addon-search": "^0.7.0",
|
||||
"xterm-addon-search-bar": "^0.2.0",
|
||||
"xterm-addon-web-links": "^0.4.0",
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -77,7 +77,7 @@ export default () => {
|
|||
</div>
|
||||
<div css={tw`mt-6`}>
|
||||
<Field
|
||||
id={'confirm_password'}
|
||||
id={'confirm_new_password'}
|
||||
type={'password'}
|
||||
name={'confirmPassword'}
|
||||
label={'Confirm New Password'}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ITerminalOptions, Terminal } from 'xterm';
|
|||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { SearchAddon } from 'xterm-addon-search';
|
||||
import { SearchBarAddon } from 'xterm-addon-search-bar';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import styled from 'styled-components/macro';
|
||||
|
@ -62,6 +63,7 @@ export default () => {
|
|||
const fitAddon = new FitAddon();
|
||||
const searchAddon = new SearchAddon();
|
||||
const searchBar = new SearchBarAddon({ searchAddon });
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
|
||||
const [ canSendCommands ] = usePermissions([ 'control.console' ]);
|
||||
const serverId = ServerContext.useStoreState(state => state.server.data!.id);
|
||||
|
@ -115,6 +117,7 @@ export default () => {
|
|||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(searchAddon);
|
||||
terminal.loadAddon(searchBar);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
fitAddon.fit();
|
||||
|
||||
// Add support for capturing keys
|
||||
|
|
|
@ -24,7 +24,7 @@ const schema = object().shape({
|
|||
.matches(/^[A-Za-z0-9_\-.]{3,48}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'),
|
||||
connectionsFrom: string()
|
||||
.required('A connection value must be provided.')
|
||||
.matches(/^([1-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'),
|
||||
.matches(/^([0-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'),
|
||||
});
|
||||
|
||||
export default () => {
|
||||
|
|
|
@ -131,7 +131,7 @@ export default ({ database, className }: Props) => {
|
|||
</div>
|
||||
</Can>
|
||||
<div css={tw`mt-6`}>
|
||||
<Label>JBDC Connection String</Label>
|
||||
<Label>JDBC Connection String</Label>
|
||||
<CopyOnClick text={`jdbc:mysql://${database.username}:${database.password}@${database.connectionString}/${database.name}`}>
|
||||
<Input
|
||||
type={'text'}
|
||||
|
|
|
@ -62,7 +62,7 @@ const EulaModalFeature = () => {
|
|||
:
|
||||
<Modal visible onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
|
||||
<FlashMessageRender key={'feature:eula'} css={tw`mb-4`}/>
|
||||
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft® EULA</h2>
|
||||
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft® EULA</h2>
|
||||
<p css={tw`text-neutral-200`}>
|
||||
By pressing {'"I Accept"'} below you are indicating your agreement to the
|
||||
<a
|
||||
|
@ -71,7 +71,7 @@ const EulaModalFeature = () => {
|
|||
rel={'noreferrer noopener'}
|
||||
href="https://account.mojang.com/documents/minecraft_eula"
|
||||
>
|
||||
Mojang EULA
|
||||
Minecraft® EULA
|
||||
</a>.
|
||||
</p>
|
||||
<div css={tw`mt-8 sm:flex items-center justify-end`}>
|
||||
|
|
|
@ -91,7 +91,7 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
<Label>Ignored Files</Label>
|
||||
<FormikFieldWrapper
|
||||
name={'payload'}
|
||||
description={'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pteroignore file will be used.'}
|
||||
description={'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pteroignore file will be used. If you have reached your backup limit, the oldest backup will be rotated.'}
|
||||
>
|
||||
<FormikField as={Textarea} name={'payload'} rows={6} />
|
||||
</FormikFieldWrapper>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<td data-attr="info-system"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total CPU Cores</td>
|
||||
<td>Total CPU Threads</td>
|
||||
<td data-attr="info-cpus"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -140,6 +140,29 @@ class AccountControllerTest extends ClientApiIntegrationTestCase
|
|||
$response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a validation error is returned to the user if no password is provided or if
|
||||
* the password is below the minimum password length.
|
||||
*/
|
||||
public function testErrorIsReturnedForInvalidRequestData()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$this->actingAs($user)->putJson('/api/client/account/password', [
|
||||
'current_password' => 'password',
|
||||
])
|
||||
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
|
||||
->assertJsonPath('errors.0.meta.rule', 'required');
|
||||
|
||||
$this->actingAs($user)->putJson('/api/client/account/password', [
|
||||
'current_password' => 'password',
|
||||
'password' => 'pass',
|
||||
'password_confirmation' => 'pass',
|
||||
])
|
||||
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
|
||||
->assertJsonPath('errors.0.meta.rule', 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a validation error is returned if the password passed in the request
|
||||
* does not have a confirmation, or the confirmation is not the same as the password.
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Commands\Server;
|
||||
|
||||
use Mockery as m;
|
||||
use Pterodactyl\Models\Node;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Validation\Factory;
|
||||
use Tests\Unit\Commands\CommandTestCase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
||||
use Pterodactyl\Console\Commands\Server\BulkPowerActionCommand;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
|
||||
class BulkPowerActionCommandTest extends CommandTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Mockery\MockInterface
|
||||
*/
|
||||
private $powerRepository;
|
||||
|
||||
/**
|
||||
* @var \Mockery\MockInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Setup test.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->powerRepository = m::mock(DaemonPowerRepository::class);
|
||||
$this->repository = m::mock(ServerRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an action can be sent to all servers.
|
||||
*/
|
||||
public function testSendAction()
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Server[] $servers */
|
||||
$servers = factory(Server::class)->times(2)->make();
|
||||
|
||||
foreach ($servers as &$server) {
|
||||
$server->setRelation('node', factory(Node::class)->make());
|
||||
}
|
||||
|
||||
$this->repository->expects('getServersForPowerActionCount')->with([], [])->andReturn(2);
|
||||
$this->repository->expects('getServersForPowerAction')->with([], [])->andReturn($servers);
|
||||
|
||||
for ($i = 0; $i < count($servers); $i++) {
|
||||
$this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull();
|
||||
}
|
||||
|
||||
$display = $this->runCommand($this->getCommand(), ['action' => 'kill'], ['yes']);
|
||||
|
||||
$this->assertNotEmpty($display);
|
||||
$this->assertStringContainsString('2/2', $display);
|
||||
$this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 2]), $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filtering servers and nodes.
|
||||
*/
|
||||
public function testSendWithFilters()
|
||||
{
|
||||
$server = factory(Server::class)->make();
|
||||
$server->setRelation('node', $node = factory(Node::class)->make());
|
||||
|
||||
$this->repository->expects('getServersForPowerActionCount')
|
||||
->with([1, 2], [3, 4])
|
||||
->andReturn(1);
|
||||
|
||||
$this->repository->expects('getServersForPowerAction')
|
||||
->with([1, 2], [3, 4])
|
||||
->andReturn(Collection::make([$server]));
|
||||
|
||||
$this->powerRepository->expects('setNode')->with($node)->andReturnSelf();
|
||||
$this->powerRepository->expects('setServer')->with($server)->andReturnSelf();
|
||||
$this->powerRepository->expects('send')->with('kill')->andReturn(new Response);
|
||||
|
||||
$display = $this->runCommand($this->getCommand(), [
|
||||
'action' => 'kill',
|
||||
'--servers' => '1,2',
|
||||
'--nodes' => '3,4',
|
||||
], ['yes']);
|
||||
|
||||
$this->assertNotEmpty($display);
|
||||
$this->assertStringContainsString('1/1', $display);
|
||||
$this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that sending empty options returns the expected results.
|
||||
*/
|
||||
public function testSendWithEmptyOptions()
|
||||
{
|
||||
$server = factory(Server::class)->make();
|
||||
$server->setRelation('node', factory(Node::class)->make());
|
||||
|
||||
$this->repository->expects('getServersForPowerActionCount')
|
||||
->with([], [])
|
||||
->andReturn(1);
|
||||
|
||||
$this->repository->expects('getServersForPowerAction')->with([], [])->andReturn(Collection::make([$server]));
|
||||
$this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull();
|
||||
|
||||
$display = $this->runCommand($this->getCommand(), [
|
||||
'action' => 'kill',
|
||||
'--servers' => '',
|
||||
'--nodes' => '',
|
||||
], ['yes']);
|
||||
|
||||
$this->assertNotEmpty($display);
|
||||
$this->assertStringContainsString('1/1', $display);
|
||||
$this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that validation occurs correctly.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @dataProvider validationFailureDataProvider
|
||||
*/
|
||||
public function testValidationErrors(array $data)
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->runCommand($this->getCommand(), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide invalid data for the command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validationFailureDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[['action' => 'hodor']],
|
||||
[['action' => 'hodor', '--servers' => 'hodor']],
|
||||
[['action' => 'kill', '--servers' => 'hodor']],
|
||||
[['action' => 'kill', '--servers' => '1,2,3', '--nodes' => 'hodor']],
|
||||
[['action' => 'kill', '--servers' => '1,2,3', '--nodes' => '1,2,test']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the command with mocked dependencies.
|
||||
*
|
||||
* @return \Pterodactyl\Console\Commands\Server\BulkPowerActionCommand
|
||||
*/
|
||||
private function getCommand(): BulkPowerActionCommand
|
||||
{
|
||||
return new BulkPowerActionCommand($this->powerRepository, $this->repository, $this->app->make(Factory::class));
|
||||
}
|
||||
}
|
|
@ -7621,6 +7621,11 @@ xterm-addon-search@^0.7.0:
|
|||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0.tgz#c929d3e5cbb335e82bff72f158ea82936d9cd4ef"
|
||||
integrity sha512-6060evmJJ+tZcjnx33FXaeEHLpuXEa7l9UzUsYfMlCKbu88AbE+5LJocTKCHYd71cwCwb9pjmv/G1o9Rf9Zbcg==
|
||||
|
||||
xterm-addon-web-links@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03"
|
||||
integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg==
|
||||
|
||||
xterm@^4.9.0:
|
||||
version "4.9.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0.tgz#7a4c097a433d565339b5533b468bbc60c6c87969"
|
||||
|
|
Loading…
Reference in New Issue