Merge branch 'develop' into feature/react-admin
This commit is contained in:
commit
49de31bf4c
24 changed files with 169 additions and 32 deletions
|
@ -31,7 +31,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
||||||
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||||
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
|
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
|
||||||
| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
|
||||||
| [**ByteAnia**](https://ByteAnia.com/) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
|
| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
||||||
|
|
|
@ -6,4 +6,11 @@ use Pterodactyl\Exceptions\DisplayException;
|
||||||
|
|
||||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* TwoFactorAuthenticationTokenInvalid constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('The provided two-factor authentication token was not valid.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ class BackupController extends ClientApiController
|
||||||
// actions against it via the Panel API.
|
// actions against it via the Panel API.
|
||||||
$server->update(['status' => Server::STATUS_RESTORING_BACKUP]);
|
$server->update(['status' => Server::STATUS_RESTORING_BACKUP]);
|
||||||
|
|
||||||
$this->repository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate') === 'true');
|
$this->repository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this->returnNoContent();
|
return $this->returnNoContent();
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
|
use Illuminate\Cache\Repository;
|
||||||
use Pterodactyl\Transformers\Api\Client\StatsTransformer;
|
use Pterodactyl\Transformers\Api\Client\StatsTransformer;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||||
|
@ -10,27 +12,35 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest;
|
||||||
|
|
||||||
class ResourceUtilizationController extends ClientApiController
|
class ResourceUtilizationController extends ClientApiController
|
||||||
{
|
{
|
||||||
|
private Repository $cache;
|
||||||
private DaemonServerRepository $repository;
|
private DaemonServerRepository $repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResourceUtilizationController constructor.
|
* ResourceUtilizationController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(DaemonServerRepository $repository)
|
public function __construct(Repository $cache, DaemonServerRepository $repository)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->cache = $cache;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current resource utilization for a server.
|
* Return the current resource utilization for a server. This value is cached for up to
|
||||||
|
* 20 seconds at a time to ensure that repeated requests to this endpoint do not cause
|
||||||
|
* a flood of unnecessary API calls.
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||||
*/
|
*/
|
||||||
public function __invoke(GetServerRequest $request, Server $server): array
|
public function __invoke(GetServerRequest $request, Server $server): array
|
||||||
{
|
{
|
||||||
$stats = $this->repository->setServer($server)->getDetails();
|
$stats = $this->cache
|
||||||
|
->tags(['resources'])
|
||||||
|
->remember($server->uuid, Carbon::now()->addSeconds(20), function () use ($server) {
|
||||||
|
return $this->repository->setServer($server)->getDetails();
|
||||||
|
});
|
||||||
|
|
||||||
return $this->fractal->item($stats)
|
return $this->fractal->item($stats)
|
||||||
->transformWith($this->getTransformer(StatsTransformer::class))
|
->transformWith($this->getTransformer(StatsTransformer::class))
|
||||||
|
|
|
@ -57,7 +57,14 @@ class TwoFactorController extends ClientApiController
|
||||||
/**
|
/**
|
||||||
* Updates a user's account to have two-factor enabled.
|
* Updates a user's account to have two-factor enabled.
|
||||||
*
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
|
||||||
|
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
|
||||||
|
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): JsonResponse
|
public function store(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
|
|
@ -122,13 +122,14 @@ class Schedule extends Model
|
||||||
* Returns the schedule's execution crontab entry as a string.
|
* Returns the schedule's execution crontab entry as a string.
|
||||||
*
|
*
|
||||||
* @return \Carbon\CarbonImmutable
|
* @return \Carbon\CarbonImmutable
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function getNextRunDate()
|
public function getNextRunDate()
|
||||||
{
|
{
|
||||||
$formatted = sprintf('%s %s %s %s %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_month, $this->cron_day_of_week);
|
$formatted = sprintf('%s %s %s %s %s', $this->cron_minute, $this->cron_hour, $this->cron_day_of_month, $this->cron_month, $this->cron_day_of_week);
|
||||||
|
|
||||||
return CarbonImmutable::createFromTimestamp(
|
return CarbonImmutable::createFromTimestamp(
|
||||||
CronExpression::factory($formatted)->getNextRunDate()->getTimestamp()
|
(new CronExpression($formatted))->getNextRunDate()->getTimestamp()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,12 @@ namespace Pterodactyl\Services\Servers;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Models\Allocation;
|
use Pterodactyl\Models\Allocation;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||||
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
|
|
||||||
class BuildModificationService
|
class BuildModificationService
|
||||||
{
|
{
|
||||||
|
@ -78,10 +80,18 @@ class BuildModificationService
|
||||||
|
|
||||||
$updateData = $this->structureService->handle($server);
|
$updateData = $this->structureService->handle($server);
|
||||||
|
|
||||||
|
// Because Wings always fetches an updated configuration from the Panel when booting
|
||||||
|
// a server this type of exception can be safely "ignored" and just written to the logs.
|
||||||
|
// Ideally this request succeedes so we can apply resource modifications on the fly
|
||||||
|
// but if it fails it isn't the end of the world.
|
||||||
if (!empty($updateData['build'])) {
|
if (!empty($updateData['build'])) {
|
||||||
$this->daemonServerRepository->setServer($server)->update([
|
try {
|
||||||
'build' => $updateData['build'],
|
$this->daemonServerRepository->setServer($server)->update([
|
||||||
]);
|
'build' => $updateData['build'],
|
||||||
|
]);
|
||||||
|
} catch (DaemonConnectionException $exception) {
|
||||||
|
Log::warning($exception, ['server_id' => $server->id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->commit();
|
$this->connection->commit();
|
||||||
|
|
|
@ -74,7 +74,7 @@ class ToggleTwoFactorService
|
||||||
$isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window'));
|
$isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window'));
|
||||||
|
|
||||||
if (!$isValidToken) {
|
if (!$isValidToken) {
|
||||||
throw new TwoFactorAuthenticationTokenInvalid('The token provided is not valid.');
|
throw new TwoFactorAuthenticationTokenInvalid();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->connection->transaction(function () use ($user, $toggleState) {
|
return $this->connection->transaction(function () use ($user, $toggleState) {
|
||||||
|
@ -94,6 +94,9 @@ class ToggleTwoFactorService
|
||||||
$inserts[] = [
|
$inserts[] = [
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'token' => password_hash($token, PASSWORD_DEFAULT),
|
'token' => password_hash($token, PASSWORD_DEFAULT),
|
||||||
|
// insert() won't actually set the time on the models, so make sure we do this
|
||||||
|
// manually here.
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$tokens[] = $token;
|
$tokens[] = $token;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": "PTDL_v1"
|
"version": "PTDL_v1"
|
||||||
},
|
},
|
||||||
"exported_at": "2020-11-03T04:22:56+00:00",
|
"exported_at": "2021-03-21T17:52:00+00:00",
|
||||||
"name": "Bungeecord",
|
"name": "Bungeecord",
|
||||||
"author": "support@pterodactyl.io",
|
"author": "support@pterodactyl.io",
|
||||||
"description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.",
|
"description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.",
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"images": ["quay.io\/pterodactyl\/core:java", "quay.io\/pterodactyl\/core:java-11"],
|
"images": ["quay.io\/pterodactyl\/core:java", "quay.io\/pterodactyl\/core:java-11"],
|
||||||
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
||||||
"config": {
|
"config": {
|
||||||
"files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_enabled\": true,\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}",
|
"files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}",
|
||||||
"startup": "{\r\n \"done\": \"Listening on \",\r\n \"userInteraction\": [\r\n \"Listening on \/0.0.0.0:25577\"\r\n ]\r\n}",
|
"startup": "{\r\n \"done\": \"Listening on \",\r\n \"userInteraction\": [\r\n \"Listening on \/0.0.0.0:25577\"\r\n ]\r\n}",
|
||||||
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}",
|
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}",
|
||||||
"stop": "end"
|
"stop": "end"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "PTDL_v1",
|
"version": "PTDL_v1",
|
||||||
"update_url": null
|
"update_url": null
|
||||||
},
|
},
|
||||||
"exported_at": "2021-02-22T19:08:49+04:00",
|
"exported_at": "2021-03-15T18:04:38+02:00",
|
||||||
"name": "Forge Minecraft",
|
"name": "Forge Minecraft",
|
||||||
"author": "support@pterodactyl.io",
|
"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.",
|
"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.",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
"quay.io\/pterodactyl\/core:java",
|
"quay.io\/pterodactyl\/core:java",
|
||||||
"quay.io\/pterodactyl\/core:java-11"
|
"quay.io\/pterodactyl\/core:java-11"
|
||||||
],
|
],
|
||||||
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
||||||
"config": {
|
"config": {
|
||||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=$(echo -e ${JSON_DATA} | jq -r '.homepage' | sed \"s\/http:\/https:\/g\")\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar",
|
"script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=$(echo -e ${JSON_DATA} | jq -r '.homepage' | sed \"s\/http:\/https:\/g\")\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar",
|
||||||
"container": "openjdk:8-jdk-slim",
|
"container": "openjdk:8-jdk-slim",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Server Version",
|
"name": "Server Version",
|
||||||
"description": "The version of Minecraft Vanilla to install. Use \"latest\" to install the latest version, or use \"snapshot\" to install the latest snapshot.",
|
"description": "The version of Minecraft Vanilla to install. Use \"latest\" to install the latest version, or use \"snapshot\" to install the latest snapshot. Go to Settings > Reinstall Server to apply.",
|
||||||
"env_variable": "VANILLA_VERSION",
|
"env_variable": "VANILLA_VERSION",
|
||||||
"default_value": "latest",
|
"default_value": "latest",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class ForceCronMonthFieldToHaveValueIfMissing extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('schedules', function (Blueprint $table) {
|
||||||
|
DB::update("UPDATE `schedules` SET `cron_month` = '*' WHERE `cron_month` = ''");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
// No down function.
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,7 +59,7 @@ services:
|
||||||
- "/srv/pterodactyl/var/:/app/var/"
|
- "/srv/pterodactyl/var/:/app/var/"
|
||||||
- "/srv/pterodactyl/nginx/:/etc/nginx/conf.d/"
|
- "/srv/pterodactyl/nginx/:/etc/nginx/conf.d/"
|
||||||
- "/srv/pterodactyl/certs/:/etc/letsencrypt/"
|
- "/srv/pterodactyl/certs/:/etc/letsencrypt/"
|
||||||
- "/srv/pterodactyl/logs/:/var/log/"
|
- "/srv/pterodactyl/logs/:/app/storage/logs"
|
||||||
environment:
|
environment:
|
||||||
<<: *panel-environment
|
<<: *panel-environment
|
||||||
<<: *mail-environment
|
<<: *mail-environment
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
|
|
||||||
export const restoreServerBackup = async (uuid: string, backup: string): Promise<void> => {
|
export const restoreServerBackup = async (uuid: string, backup: string, truncate?: boolean): Promise<void> => {
|
||||||
await http.post(`/api/client/servers/${uuid}/backups/${backup}/restore`);
|
await http.post(`/api/client/servers/${uuid}/backups/${backup}/restore`, {
|
||||||
|
truncate,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ const Container = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const state = useLocation<{ twoFactorRedirect: boolean }>().state;
|
const { state } = useLocation<undefined | { twoFactorRedirect?: boolean }>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContentBlock title={'Account Overview'}>
|
<PageContentBlock title={'Account Overview'}>
|
||||||
|
|
|
@ -12,10 +12,14 @@ import tw from 'twin.macro';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { PaginatedResult } from '@/api/http';
|
import { PaginatedResult } from '@/api/http';
|
||||||
import Pagination from '@/components/elements/Pagination';
|
import Pagination from '@/components/elements/Pagination';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const { search } = useLocation();
|
||||||
|
const defaultPage = Number(new URLSearchParams(search).get('page') || '1');
|
||||||
|
|
||||||
|
const [ page, setPage ] = useState((!isNaN(defaultPage) && defaultPage > 0) ? defaultPage : 1);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const [ page, setPage ] = useState(1);
|
|
||||||
const uuid = useStoreState(state => state.user.data!.uuid);
|
const uuid = useStoreState(state => state.user.data!.uuid);
|
||||||
const rootAdmin = useStoreState(state => state.user.data!.rootAdmin);
|
const rootAdmin = useStoreState(state => state.user.data!.rootAdmin);
|
||||||
const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState(`${uuid}:show_all_servers`, false);
|
const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState(`${uuid}:show_all_servers`, false);
|
||||||
|
@ -25,6 +29,20 @@ export default () => {
|
||||||
() => getServers({ page, type: showOnlyAdmin ? 'admin' : undefined }),
|
() => getServers({ page, type: showOnlyAdmin ? 'admin' : undefined }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!servers) return;
|
||||||
|
if (servers.pagination.currentPage > 1 && !servers.items.length) {
|
||||||
|
setPage(1);
|
||||||
|
}
|
||||||
|
}, [ servers?.pagination.currentPage ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Don't use react-router to handle changing this part of the URL, otherwise it
|
||||||
|
// triggers a needless re-render. We just want to track this in the URL incase the
|
||||||
|
// user refreshes the page.
|
||||||
|
window.history.replaceState(null, document.title, `/${page <= 1 ? '' : `?page=${page}`}`);
|
||||||
|
}, [ page ]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) clearAndAddHttpError({ key: 'dashboard', error });
|
if (error) clearAndAddHttpError({ key: 'dashboard', error });
|
||||||
if (!error) clearFlashes('dashboard');
|
if (!error) clearFlashes('dashboard');
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
||||||
|
|
||||||
getStats().then(() => {
|
getStats().then(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
interval.current = setInterval(() => getStats(), 20000);
|
interval.current = setInterval(() => getStats(), 30000);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default ({ onDismissed, ...props }: RequiredModalProps) => {
|
||||||
<h2 css={tw`text-2xl mb-4`}>Two-factor authentication enabled</h2>
|
<h2 css={tw`text-2xl mb-4`}>Two-factor authentication enabled</h2>
|
||||||
<p css={tw`text-neutral-300`}>
|
<p css={tw`text-neutral-300`}>
|
||||||
Two-factor authentication has been enabled on your account. Should you loose access to
|
Two-factor authentication has been enabled on your account. Should you loose access to
|
||||||
this device you'll need to use on of the codes displayed below in order to access your
|
this device you'll need to use one of the codes displayed below in order to access your
|
||||||
account.
|
account.
|
||||||
</p>
|
</p>
|
||||||
<p css={tw`text-neutral-300 mt-4`}>
|
<p css={tw`text-neutral-300 mt-4`}>
|
||||||
|
|
|
@ -146,10 +146,10 @@ export default () => {
|
||||||
|
|
||||||
// Add support for capturing keys
|
// Add support for capturing keys
|
||||||
terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => {
|
terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => {
|
||||||
if (e.metaKey && e.key === 'c') {
|
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
return false;
|
return false;
|
||||||
} else if (e.metaKey && e.key === 'f') {
|
} else if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
searchBar.show();
|
searchBar.show();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default ({ backup }: Props) => {
|
||||||
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
|
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
|
||||||
const [ modal, setModal ] = useState('');
|
const [ modal, setModal ] = useState('');
|
||||||
const [ loading, setLoading ] = useState(false);
|
const [ loading, setLoading ] = useState(false);
|
||||||
|
const [ truncate, setTruncate ] = useState(false);
|
||||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||||
const { mutate } = getServerBackups();
|
const { mutate } = getServerBackups();
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ export default ({ backup }: Props) => {
|
||||||
const doRestorationAction = () => {
|
const doRestorationAction = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
clearFlashes('backups');
|
clearFlashes('backups');
|
||||||
restoreServerBackup(uuid, backup.uuid)
|
restoreServerBackup(uuid, backup.uuid, truncate)
|
||||||
.then(() => setServerFromState(s => ({
|
.then(() => setServerFromState(s => ({
|
||||||
...s,
|
...s,
|
||||||
status: 'restoring_backup',
|
status: 'restoring_backup',
|
||||||
|
@ -108,6 +109,8 @@ export default ({ backup }: Props) => {
|
||||||
css={tw`text-red-500! w-5! h-5! mr-2`}
|
css={tw`text-red-500! w-5! h-5! mr-2`}
|
||||||
id={'restore_truncate'}
|
id={'restore_truncate'}
|
||||||
value={'true'}
|
value={'true'}
|
||||||
|
checked={truncate}
|
||||||
|
onChange={() => setTruncate(s => !s)}
|
||||||
/>
|
/>
|
||||||
Remove all files and folders before restoring this backup.
|
Remove all files and folders before restoring this backup.
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -78,7 +78,7 @@ const StartupContainer = () => {
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<ServerContentBlock title={'Startup Settings'} showFlashKey={'startup:image'}>
|
<ServerContentBlock title={'Startup Settings'} showFlashKey={'startup:image'}>
|
||||||
<div css={tw`flex`}>
|
<div css={tw`md:flex`}>
|
||||||
<TitledGreyBox title={'Startup Command'} css={tw`flex-1`}>
|
<TitledGreyBox title={'Startup Command'} css={tw`flex-1`}>
|
||||||
<div css={tw`px-1 py-2`}>
|
<div css={tw`px-1 py-2`}>
|
||||||
<p css={tw`font-mono bg-neutral-900 rounded py-2 px-4`}>
|
<p css={tw`font-mono bg-neutral-900 rounded py-2 px-4`}>
|
||||||
|
@ -86,7 +86,7 @@ const StartupContainer = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</TitledGreyBox>
|
</TitledGreyBox>
|
||||||
<TitledGreyBox title={'Docker Image'} css={tw`flex-1 lg:flex-none lg:w-1/3 ml-10`}>
|
<TitledGreyBox title={'Docker Image'} css={tw`flex-1 lg:flex-none lg:w-1/3 mt-8 md:mt-0 md:ml-10`}>
|
||||||
{data.dockerImages.length > 1 && !isCustomImage ?
|
{data.dockerImages.length > 1 && !isCustomImage ?
|
||||||
<>
|
<>
|
||||||
<InputSpinner visible={loading}>
|
<InputSpinner visible={loading}>
|
||||||
|
|
|
@ -21,10 +21,18 @@ export default ({ location }: RouteComponentProps) => (
|
||||||
}
|
}
|
||||||
<TransitionRouter>
|
<TransitionRouter>
|
||||||
<Switch location={location}>
|
<Switch location={location}>
|
||||||
<Route path={'/'} component={DashboardContainer} exact/>
|
<Route path={'/'} exact>
|
||||||
<Route path={'/account'} component={AccountOverviewContainer} exact/>
|
<DashboardContainer/>
|
||||||
<Route path={'/account/api'} component={AccountApiContainer} exact/>
|
</Route>
|
||||||
<Route path={'*'} component={NotFound}/>
|
<Route path={'/account'} exact>
|
||||||
|
<AccountOverviewContainer/>
|
||||||
|
</Route>
|
||||||
|
<Route path={'/account/api'} exact>
|
||||||
|
<AccountApiContainer/>
|
||||||
|
</Route>
|
||||||
|
<Route path={'*'}>
|
||||||
|
<NotFound/>
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</TransitionRouter>
|
</TransitionRouter>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -101,6 +101,11 @@ class TwoFactorControllerTest extends ClientApiIntegrationTestCase
|
||||||
$tokens = RecoveryToken::query()->where('user_id', $user->id)->get();
|
$tokens = RecoveryToken::query()->where('user_id', $user->id)->get();
|
||||||
$this->assertCount(10, $tokens);
|
$this->assertCount(10, $tokens);
|
||||||
$this->assertStringStartsWith('$2y$10$', $tokens[0]->token);
|
$this->assertStringStartsWith('$2y$10$', $tokens[0]->token);
|
||||||
|
// Ensure the recovery tokens that were created include a "created_at" timestamp
|
||||||
|
// value on them.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/3163
|
||||||
|
$this->assertNotNull($tokens[0]->created_at);
|
||||||
|
|
||||||
$tokens = $tokens->pluck('token')->toArray();
|
$tokens = $tokens->pluck('token')->toArray();
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
namespace Pterodactyl\Tests\Integration\Services\Servers;
|
namespace Pterodactyl\Tests\Integration\Services\Servers;
|
||||||
|
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Models\Allocation;
|
use Pterodactyl\Models\Allocation;
|
||||||
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
use GuzzleHttp\Exception\TransferException;
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
use Pterodactyl\Tests\Integration\IntegrationTestCase;
|
use Pterodactyl\Tests\Integration\IntegrationTestCase;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||||
use Pterodactyl\Services\Servers\BuildModificationService;
|
use Pterodactyl\Services\Servers\BuildModificationService;
|
||||||
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
|
|
||||||
class BuildModificationServiceTest extends IntegrationTestCase
|
class BuildModificationServiceTest extends IntegrationTestCase
|
||||||
{
|
{
|
||||||
|
@ -149,6 +154,30 @@ class BuildModificationServiceTest extends IntegrationTestCase
|
||||||
$this->assertSame(20, $response->allocation_limit);
|
$this->assertSame(20, $response->allocation_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that an exception when connecting to the Wings instance is properly ignored
|
||||||
|
* when making updates. This allows for a server to be modified even when the Wings
|
||||||
|
* node is offline.
|
||||||
|
*/
|
||||||
|
public function testConnectionExceptionIsIgnoredWhenUpdatingServerSettings()
|
||||||
|
{
|
||||||
|
$server = $this->createServerModel();
|
||||||
|
|
||||||
|
$this->daemonServerRepository->expects('setServer->update')->andThrows(
|
||||||
|
new DaemonConnectionException(
|
||||||
|
new RequestException('Bad request', new Request('GET', '/test'), new Response())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $this->getService()->handle($server, ['memory' => 256, 'disk' => 10240]);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Server::class, $response);
|
||||||
|
$this->assertSame(256, $response->memory);
|
||||||
|
$this->assertSame(10240, $response->disk);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('servers', ['id' => $response->id, 'memory' => 256, 'disk' => 10240]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that no exception is thrown if we are only removing an allocation.
|
* Test that no exception is thrown if we are only removing an allocation.
|
||||||
*/
|
*/
|
||||||
|
@ -215,7 +244,9 @@ class BuildModificationServiceTest extends IntegrationTestCase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that any changes we made to the server or allocations are rolled back if there is an
|
* Test that any changes we made to the server or allocations are rolled back if there is an
|
||||||
* exception while performing any action.
|
* exception while performing any action. This is different than the connection exception
|
||||||
|
* test which should properly ignore connection issues. We want any other type of exception
|
||||||
|
* to properly be thrown back to the caller.
|
||||||
*/
|
*/
|
||||||
public function testThatUpdatesAreRolledBackIfExceptionIsEncountered()
|
public function testThatUpdatesAreRolledBackIfExceptionIsEncountered()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue