forked from Alex/Pterodactyl-Panel
Compare commits
28 Commits
release/v1
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
1d02365efe | ||
|
1564742606 | ||
|
66c56b0da8 | ||
|
15619fb8e4 | ||
|
4e6fe112b0 | ||
|
f04b87a37c | ||
|
928b060647 | ||
|
b8bf537737 | ||
|
0d5ff6afac | ||
|
5cde059f21 | ||
|
0db772a82b | ||
|
dcbc1360a9 | ||
|
622b939f00 | ||
|
e8e2911a92 | ||
|
96c3338e96 | ||
|
49d5ef271d | ||
|
4cc8658334 | ||
|
a6e0e5dbda | ||
|
10aaf00e83 | ||
|
b9d73afb63 | ||
|
59d47e746b | ||
|
01e7a45cc5 | ||
|
30bb629bad | ||
|
bf9cbe2c6d | ||
|
cc31a0a6d0 | ||
|
01871d8a6c | ||
|
17c03e9a4d | ||
|
e8a8405899 |
@ -8,6 +8,7 @@ APP_DELETE_MINUTES=10
|
||||
APP_ENVIRONMENT_ONLY=true
|
||||
LOG_CHANNEL=daily
|
||||
APP_LOCALE=en
|
||||
APP_URL=http://panel.example.com
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
@ -30,7 +31,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
|
||||
# mail servers such as Gmail to reject your mail.
|
||||
#
|
||||
# @see: https://github.com/pterodactyl/panel/pull/3110
|
||||
# SERVER_NAME=panel.yourdomain.com
|
||||
# SERVER_NAME=panel.example.com
|
||||
|
||||
QUEUE_HIGH=high
|
||||
QUEUE_STANDARD=standard
|
||||
|
2
.github/docker/README.md
vendored
2
.github/docker/README.md
vendored
@ -33,7 +33,7 @@ Note: If your `APP_URL` starts with `https://` you need to provide an `LETSENCRY
|
||||
| ------------------- | ------------------------------------------------------------------------------ | -------- |
|
||||
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
|
||||
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
|
||||
| `LETSENCRYPT_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
||||
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
||||
| `DB_HOST` | The host of the mysql instance | yes |
|
||||
| `DB_PORT` | The port of the mysql instance | yes |
|
||||
| `DB_DATABASE` | The name of the mysql database | yes |
|
||||
|
2
.github/docker/entrypoint.sh
vendored
2
.github/docker/entrypoint.sh
vendored
@ -57,7 +57,7 @@ fi
|
||||
|
||||
## check for DB up before starting the panel
|
||||
echo "Checking database status."
|
||||
until nc -z -v -w30 $DB_HOST 3306
|
||||
until nc -z -v -w30 $DB_HOST $DB_PORT
|
||||
do
|
||||
echo "Waiting for database connection..."
|
||||
# wait for 1 seconds before check again
|
||||
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -36,6 +36,7 @@ jobs:
|
||||
if: "!contains(github.ref, 'develop')"
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
- name: Release Development Build
|
||||
@ -43,5 +44,6 @@ jobs:
|
||||
if: "contains(github.ref, 'develop')"
|
||||
with:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -3,7 +3,20 @@ 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.6.6
|
||||
### Fixed
|
||||
* **[security]** Fixes a CSRF vulnerability for both the administrative test email endpoint and node auto-deployment token generation endpoint. [GHSA-wwgq-9jhf-qgw6](https://github.com/pterodactyl/panel/security/advisories/GHSA-wwgq-9jhf-qgw6)
|
||||
|
||||
### Changed
|
||||
* Updates Minecraft eggs to include latest Java 17 yolk by default.
|
||||
|
||||
## v1.6.5
|
||||
### Fixed
|
||||
* Fixes broken application API endpoints due to changes introduced with session management in 1.6.4.
|
||||
|
||||
## v1.6.4
|
||||
_This release should not be used, please use `1.6.5`. It has been pulled from our releases._
|
||||
|
||||
### Fixed
|
||||
* Fixes a session management bug that would cause a user who signs out of one browser to be unintentionally logged out of other browser sessions when using the client API.
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
# Build the assets that are needed for the frontend. This build stage is then discarded
|
||||
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
|
||||
# level distribution of Pterodactyl.
|
||||
FROM mhart/alpine-node:14
|
||||
FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN yarn install --frozen-lockfile \
|
||||
@ -10,7 +10,7 @@ RUN yarn install --frozen-lockfile \
|
||||
|
||||
# Stage 1:
|
||||
# Build the actual container with all of the needed PHP dependencies that will run the application.
|
||||
FROM php:7.4-fpm-alpine
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:7.4-fpm-alpine
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
COPY --from=0 /app/public/assets ./public/assets
|
||||
|
@ -37,6 +37,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
|
||||
| [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! |
|
||||
| [**HostEZ**](https://hostez.io) | Providing North America Valheim, Minecraft and other popular games with low latency, high uptime and maximum availability. EZ! |
|
||||
|
||||
## Documentation
|
||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
||||
|
@ -12,6 +12,7 @@ namespace Pterodactyl\Console\Commands\Environment;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
|
||||
@ -78,12 +79,13 @@ class AppSettingsCommand extends Command
|
||||
/**
|
||||
* AppSettingsCommand constructor.
|
||||
*/
|
||||
public function __construct(ConfigRepository $config, Kernel $command)
|
||||
public function __construct(ConfigRepository $config, Kernel $command, ValidatorFactory $validator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->command = $command;
|
||||
$this->config = $config;
|
||||
$this->command = $command;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,6 +105,18 @@ class AppSettingsCommand extends Command
|
||||
$this->config->get('pterodactyl.service.author', 'unknown@unknown.com')
|
||||
);
|
||||
|
||||
$validator = $this->validator->make(
|
||||
['email' => $this->variables['APP_SERVICE_AUTHOR']],
|
||||
['email' => 'email']
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
foreach ($validator->errors()->all() as $error) {
|
||||
$this->output->error($error);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->output->comment(trans('command/messages.environment.app.app_url_help'));
|
||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||
trans('command/messages.environment.app.app_url'),
|
||||
|
@ -70,9 +70,12 @@ class Kernel extends HttpKernel
|
||||
'api' => [
|
||||
HandleStatelessRequest::class,
|
||||
IsValidJson::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ApiSubstituteBindings::class,
|
||||
'api..key:' . ApiKey::TYPE_APPLICATION,
|
||||
AuthenticateApplicationUser::class,
|
||||
VerifyCsrfToken::class,
|
||||
AuthenticateIPAccess::class,
|
||||
],
|
||||
'client-api' => [
|
||||
@ -83,6 +86,7 @@ class Kernel extends HttpKernel
|
||||
SubstituteClientApiBindings::class,
|
||||
'api..key:' . ApiKey::TYPE_ACCOUNT,
|
||||
AuthenticateIPAccess::class,
|
||||
VerifyCsrfToken::class,
|
||||
// This is perhaps a little backwards with the Client API, but logically you'd be unable
|
||||
// to create/get an API key without first enabling 2FA on the account, so I suppose in the
|
||||
// end it makes sense.
|
||||
|
@ -8,6 +8,7 @@ use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
@ -55,7 +56,7 @@ class AuthenticateKey
|
||||
public function handle(Request $request, Closure $next, int $keyType)
|
||||
{
|
||||
if (is_null($request->bearerToken()) && is_null($request->user())) {
|
||||
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
|
||||
throw new HttpException(401, 'A bearer token or valid user session cookie must be provided to access this endpoint.', null, ['WWW-Authenticate' => 'Bearer']);
|
||||
}
|
||||
|
||||
// This is a request coming through using cookies, we have an authenticated user
|
||||
|
@ -2,18 +2,45 @@
|
||||
|
||||
namespace Pterodactyl\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
|
||||
|
||||
class VerifyCsrfToken extends BaseVerifier
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
* The URIs that should be excluded from CSRF verification. These are
|
||||
* never hit by the front-end, and require specific token validation
|
||||
* to work.
|
||||
*
|
||||
* @var array
|
||||
* @var string[]
|
||||
*/
|
||||
protected $except = [
|
||||
'remote/*',
|
||||
'daemon/*',
|
||||
'api/*',
|
||||
];
|
||||
protected $except = ['remote/*', 'daemon/*'];
|
||||
|
||||
/**
|
||||
* Manually apply CSRF protection to routes depending on the authentication
|
||||
* mechanism being used. If the API request is using an API key that exists
|
||||
* in the database we can safely ignore CSRF protections, since that would be
|
||||
* a manually initiated request by a user or server.
|
||||
*
|
||||
* All other requests should go through the standard CSRF protections that
|
||||
* Laravel affords us. This code will be removed in v2 since we have switched
|
||||
* to using Sanctum for the API endpoints, which handles that for us automatically.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Session\TokenMismatchException
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$key = $request->attributes->get('api_key');
|
||||
|
||||
if ($key instanceof ApiKey && $key->exists) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return parent::handle($request, $next);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-07-04T19:18:34-04:00",
|
||||
"exported_at": "2021-11-14T19:23:12+00:00",
|
||||
"name": "Bungeecord",
|
||||
"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.",
|
||||
@ -15,14 +15,15 @@
|
||||
"images": [
|
||||
"ghcr.io\/pterodactyl\/yolks:java_8",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_11",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16"
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_17"
|
||||
],
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"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}",
|
||||
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "end"
|
||||
},
|
||||
"scripts": {
|
||||
|
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-10-22T19:19:11+02:00",
|
||||
"exported_at": "2021-11-14T19:21:07+00:00",
|
||||
"name": "Paper",
|
||||
"author": "parker@pterodactyl.io",
|
||||
"description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.",
|
||||
@ -15,7 +15,8 @@
|
||||
"images": [
|
||||
"ghcr.io\/pterodactyl\/yolks:java_8",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_11",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16"
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_17"
|
||||
],
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-10-22T19:19:23+02:00",
|
||||
"exported_at": "2021-11-14T19:18:30+00:00",
|
||||
"name": "Vanilla Minecraft",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.",
|
||||
@ -15,14 +15,15 @@
|
||||
"images": [
|
||||
"ghcr.io\/pterodactyl\/yolks:java_8",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_11",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16"
|
||||
"ghcr.io\/pterodactyl\/yolks:java_16",
|
||||
"ghcr.io\/pterodactyl\/yolks:java_17"
|
||||
],
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
|
||||
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "stop"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -48,7 +48,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Server Map",
|
||||
"description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2",
|
||||
"description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2, LostIsland",
|
||||
"env_variable": "SERVER_MAP",
|
||||
"default_value": "TheIsland",
|
||||
"user_viewable": true,
|
||||
@ -119,4 +119,4 @@
|
||||
"rules": "nullable|string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,13 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-06-05T16:19:30-04:00",
|
||||
"exported_at": "2021-09-10T14:36:37-04:00",
|
||||
"name": "Counter-Strike: Global Offensive",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.",
|
||||
"features": null,
|
||||
"features": [
|
||||
"gsl_token"
|
||||
],
|
||||
"images": [
|
||||
"ghcr.io\/pterodactyl\/games:source"
|
||||
],
|
||||
@ -16,19 +18,18 @@
|
||||
"startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
||||
"config": {
|
||||
"files": "{}",
|
||||
"startup": "{\r\n \"done\": \"Connection to Steam servers successful\",\r\n \"userInteraction\": []\r\n}",
|
||||
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}",
|
||||
"startup": "{\r\n \"done\": \"Connection to Steam servers successful\"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "quit"
|
||||
},
|
||||
"scripts": {
|
||||
"installation": {
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||
"entrypoint": "bash"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"variables": [{
|
||||
"name": "Map",
|
||||
"description": "The default map for the server.",
|
||||
"env_variable": "SRCDS_MAP",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-06-05T16:24:05-04:00",
|
||||
"exported_at": "2021-09-10T14:36:22-04:00",
|
||||
"name": "Custom Source Engine Game",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "This option allows modifying the startup arguments and other details to run a custom SRCDS based game on the panel.",
|
||||
@ -16,19 +16,18 @@
|
||||
"startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
|
||||
"config": {
|
||||
"files": "{}",
|
||||
"startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}",
|
||||
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}",
|
||||
"startup": "{\r\n \"done\": \"gameserver Steam ID\"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "quit"
|
||||
},
|
||||
"scripts": {
|
||||
"installation": {
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||
"entrypoint": "bash"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"variables": [{
|
||||
"name": "Game ID",
|
||||
"description": "The ID corresponding to the game to download and run using SRCDS.",
|
||||
"env_variable": "SRCDS_APPID",
|
||||
@ -83,4 +82,4 @@
|
||||
"rules": "nullable|string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,13 @@
|
||||
"version": "PTDL_v1",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2021-08-27T00:12:31-04:00",
|
||||
"exported_at": "2021-12-04T18:47:10+00:00",
|
||||
"name": "Garrys Mod",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.",
|
||||
"features": null,
|
||||
"features": [
|
||||
"gsl_token"
|
||||
],
|
||||
"images": [
|
||||
"ghcr.io\/pterodactyl\/games:source"
|
||||
],
|
||||
@ -22,7 +24,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"installation": {
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
|
||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
|
||||
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||
"entrypoint": "bash"
|
||||
}
|
||||
@ -101,4 +103,4 @@
|
||||
"rules": "required|boolean"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ x-common:
|
||||
#
|
||||
services:
|
||||
database:
|
||||
image: library/mysql:8.0
|
||||
image: mariadb:10.5
|
||||
restart: always
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
volumes:
|
||||
|
@ -37,7 +37,7 @@
|
||||
"swr": "^0.2.3",
|
||||
"tailwindcss": "^2.0.2",
|
||||
"uuid": "^3.3.2",
|
||||
"xterm": "^4.12.0",
|
||||
"xterm": "^4.15.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.4.0",
|
||||
"xterm-addon-search": "^0.7.0",
|
||||
|
@ -7,10 +7,21 @@ const http: AxiosInstance = axios.create({
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': (window as any).X_CSRF_TOKEN as string || '',
|
||||
},
|
||||
});
|
||||
|
||||
http.interceptors.request.use(req => {
|
||||
const cookies = document.cookie.split(';').reduce((obj, val) => {
|
||||
const [ key, value ] = val.trim().split('=').map(decodeURIComponent);
|
||||
|
||||
return { ...obj, [key]: value };
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
req.headers['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'] || 'nil';
|
||||
|
||||
return req;
|
||||
});
|
||||
|
||||
http.interceptors.request.use(req => {
|
||||
if (!req.url?.endsWith('/resources') && (req.url?.indexOf('_debugbar') || -1) < 0) {
|
||||
store.getActions().progress.startContinuous();
|
||||
|
@ -47,7 +47,7 @@ export default () => {
|
||||
>
|
||||
<UpdateEmailAddressForm/>
|
||||
</ContentBox>
|
||||
<ContentBox css={tw`xl:ml-8 mt-8 xl:mt-0`} title={'Configure Two Factor'}>
|
||||
<ContentBox css={tw`lg:ml-8 mt-8 lg:mt-0`} title={'Configure Two Factor'}>
|
||||
<ConfigureTwoFactorForm/>
|
||||
</ContentBox>
|
||||
</Container>
|
||||
|
@ -4,7 +4,7 @@ import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Server } from '@/api/server/getServer';
|
||||
import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/server/getServerResourceUsage';
|
||||
import { bytesToHuman, megabytesToHuman } from '@/helpers';
|
||||
import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers';
|
||||
import tw from 'twin.macro';
|
||||
import GreyRowBox from '@/components/elements/GreyRowBox';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
@ -97,7 +97,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
||||
{
|
||||
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
|
||||
<React.Fragment key={allocation.ip + allocation.port.toString()}>
|
||||
{allocation.alias || allocation.ip}:{allocation.port}
|
||||
{allocation.alias || formatIp(allocation.ip)}:{allocation.port}
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components/macro';
|
||||
import tw from 'twin.macro';
|
||||
import Input from '@/components/elements/Input';
|
||||
|
||||
import { formatIp } from '@/helpers';
|
||||
type Props = RequiredModalProps;
|
||||
|
||||
interface Values {
|
||||
@ -109,7 +109,7 @@ export default ({ ...props }: Props) => {
|
||||
<p css={tw`mt-1 text-xs text-neutral-400`}>
|
||||
{
|
||||
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
|
||||
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
|
||||
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || formatIp(allocation.ip)}:{allocation.port}</span>
|
||||
))
|
||||
}
|
||||
</p>
|
||||
|
@ -7,7 +7,7 @@ import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||
import ServerDetailsBlock from '@/components/server/ServerDetailsBlock';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import PowerControls from '@/components/server/PowerControls';
|
||||
import { EulaModalFeature, JavaVersionModalFeature } from '@feature/index';
|
||||
import { EulaModalFeature, JavaVersionModalFeature, GSLTokenModalFeature } from '@feature/index';
|
||||
import ErrorBoundary from '@/components/elements/ErrorBoundary';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
|
||||
@ -60,6 +60,7 @@ const ServerConsole = () => {
|
||||
<React.Suspense fallback={null}>
|
||||
{eggFeatures.includes('eula') && <EulaModalFeature/>}
|
||||
{eggFeatures.includes('java_version') && <JavaVersionModalFeature/>}
|
||||
{eggFeatures.includes('gsl_token') && <GSLTokenModalFeature/>}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</ServerContentBlock>
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import tw, { TwStyle } from 'twin.macro';
|
||||
import { faCircle, faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { bytesToHuman, megabytesToHuman } from '@/helpers';
|
||||
import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers';
|
||||
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
@ -72,7 +72,7 @@ const ServerDetailsBlock = () => {
|
||||
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
|
||||
const limits = ServerContext.useStoreState(state => state.server.data!.limits);
|
||||
const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map(
|
||||
allocation => (allocation.alias || allocation.ip) + ':' + allocation.port,
|
||||
allocation => (allocation.alias || formatIp(allocation.ip)) + ':' + allocation.port,
|
||||
)).toString();
|
||||
|
||||
const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited';
|
||||
|
@ -97,6 +97,7 @@ export default () => {
|
||||
setCpu(
|
||||
new Chart(node.getContext('2d')!, chartDefaults({
|
||||
callback: (value) => `${value}% `,
|
||||
suggestedMax: limits.cpu,
|
||||
})),
|
||||
);
|
||||
}, []);
|
||||
|
@ -76,7 +76,7 @@ export default ({ database, className }: Props) => {
|
||||
<FlashMessageRender byKey={'database:delete'} css={tw`mb-6`}/>
|
||||
<h2 css={tw`text-2xl mb-6`}>Confirm database deletion</h2>
|
||||
<p css={tw`text-sm`}>
|
||||
Deleting a database is a permanent action, it cannot be undone. This will permanetly
|
||||
Deleting a database is a permanent action, it cannot be undone. This will permanently
|
||||
delete the <strong>{database.name}</strong> database and remove all associated data.
|
||||
</p>
|
||||
<Form css={tw`m-0 mt-6`}>
|
||||
|
@ -0,0 +1,101 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import Modal from '@/components/elements/Modal';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import { SocketEvent, SocketRequest } from '@/components/server/events';
|
||||
import Field from '@/components/elements/Field';
|
||||
import updateStartupVariable from '@/api/server/updateStartupVariable';
|
||||
import { Form, Formik } from 'formik';
|
||||
|
||||
interface Values {
|
||||
gslToken: string;
|
||||
}
|
||||
|
||||
const GSLTokenModalFeature = () => {
|
||||
const [ visible, setVisible ] = useState(false);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const status = ServerContext.useStoreState(state => state.status.value);
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected || !instance || status === 'running') return;
|
||||
|
||||
const errors = [
|
||||
'(gsl token expired)',
|
||||
'(account not found)',
|
||||
];
|
||||
|
||||
const listener = (line: string) => {
|
||||
if (errors.some(p => line.toLowerCase().includes(p))) {
|
||||
setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
instance.addListener(SocketEvent.CONSOLE_OUTPUT, listener);
|
||||
|
||||
return () => {
|
||||
instance.removeListener(SocketEvent.CONSOLE_OUTPUT, listener);
|
||||
};
|
||||
}, [ connected, instance, status ]);
|
||||
|
||||
const updateGSLToken = (values: Values) => {
|
||||
setLoading(true);
|
||||
clearFlashes('feature:gslToken');
|
||||
|
||||
updateStartupVariable(uuid, 'STEAM_ACC', values.gslToken)
|
||||
.then(() => {
|
||||
if (instance) {
|
||||
instance.send(SocketRequest.SET_STATE, 'restart');
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
setVisible(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'feature:gslToken', error });
|
||||
})
|
||||
.then(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
clearFlashes('feature:gslToken');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
onSubmit={updateGSLToken}
|
||||
initialValues={{ gslToken: '' }}
|
||||
>
|
||||
<Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
|
||||
<FlashMessageRender key={'feature:gslToken'} css={tw`mb-4`}/>
|
||||
<Form>
|
||||
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Invalid GSL token!</h2>
|
||||
<p css={tw`mt-4`}>It seems like your Gameserver Login Token (GSL token) is invalid or has expired.</p>
|
||||
<p css={tw`mt-4`}>You can either generate a new one and enter it below or leave the field blank to remove it completely.</p>
|
||||
<div css={tw`sm:flex items-center mt-4`}>
|
||||
<Field
|
||||
name={'gslToken'}
|
||||
label={'GSL Token'}
|
||||
description={'Visit https://steamcommunity.com/dev/managegameservers to generate a token.'}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div css={tw`mt-8 sm:flex items-center justify-end`}>
|
||||
<Button type={'submit'} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
|
||||
Update GSL Token
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default GSLTokenModalFeature;
|
@ -10,15 +10,16 @@ import { SocketEvent, SocketRequest } from '@/components/server/events';
|
||||
import Select from '@/components/elements/Select';
|
||||
|
||||
const dockerImageList = [
|
||||
{ name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' },
|
||||
{ name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' },
|
||||
{ name: 'Java 17', image: 'ghcr.io/pterodactyl/yolks:java_17' },
|
||||
{ name: 'Java 16', image: 'ghcr.io/pterodactyl/yolks:java_16' },
|
||||
{ name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' },
|
||||
{ name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' },
|
||||
];
|
||||
|
||||
const JavaVersionModalFeature = () => {
|
||||
const [ visible, setVisible ] = useState(false);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_16');
|
||||
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_17');
|
||||
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const status = ServerContext.useStoreState(state => state.status.value);
|
||||
@ -30,6 +31,7 @@ const JavaVersionModalFeature = () => {
|
||||
|
||||
const errors = [
|
||||
'minecraft 1.17 requires running the server with java 16 or above',
|
||||
'minecraft 1.18 requires running the server with java 17 or above',
|
||||
'java.lang.unsupportedclassversionerror',
|
||||
'unsupported major.minor version',
|
||||
'has been compiled by a more recent version of the java runtime',
|
||||
|
@ -8,5 +8,6 @@ import { lazy } from 'react';
|
||||
*/
|
||||
const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'@feature/eula/EulaModalFeature'));
|
||||
const JavaVersionModalFeature = lazy(() => import(/* webpackChunkName: "feature.java_version" */'@feature/JavaVersionModalFeature'));
|
||||
const GSLTokenModalFeature = lazy(() => import(/* webpackChunkName: "feature.gsl_token" */'@feature/GSLTokenModalFeature'));
|
||||
|
||||
export { EulaModalFeature, JavaVersionModalFeature };
|
||||
export { EulaModalFeature, JavaVersionModalFeature, GSLTokenModalFeature };
|
||||
|
@ -18,6 +18,7 @@ import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
import DeleteAllocationButton from '@/components/server/network/DeleteAllocationButton';
|
||||
import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation';
|
||||
import getServerAllocations from '@/api/swr/getServerAllocations';
|
||||
import { formatIp } from '@/helpers';
|
||||
|
||||
const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm inline-block`}`;
|
||||
const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`;
|
||||
@ -66,7 +67,7 @@ const AllocationRow = ({ allocation }: Props) => {
|
||||
<div css={tw`mr-4 flex-1 md:w-40`}>
|
||||
{allocation.alias ?
|
||||
<CopyOnClick text={allocation.alias}><Code css={tw`w-40 truncate`}>{allocation.alias}</Code></CopyOnClick> :
|
||||
<CopyOnClick text={allocation.ip}><Code>{allocation.ip}</Code></CopyOnClick>}
|
||||
<CopyOnClick text={formatIp(allocation.ip)}><Code>{formatIp(allocation.ip)}</Code></CopyOnClick>}
|
||||
<Label>{allocation.alias ? 'Hostname' : 'IP Address'}</Label>
|
||||
</div>
|
||||
<div css={tw`w-16 md:w-24 overflow-hidden`}>
|
||||
|
@ -13,6 +13,7 @@ import { LinkButton } from '@/components/elements/Button';
|
||||
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import CopyOnClick from '@/components/elements/CopyOnClick';
|
||||
import { formatIp } from '@/helpers';
|
||||
|
||||
export default () => {
|
||||
const username = useStoreState(state => state.user.data!.username);
|
||||
@ -30,10 +31,10 @@ export default () => {
|
||||
<TitledGreyBox title={'SFTP Details'} css={tw`mb-6 md:mb-10`}>
|
||||
<div>
|
||||
<Label>Server Address</Label>
|
||||
<CopyOnClick text={`sftp://${sftp.ip}:${sftp.port}`}>
|
||||
<CopyOnClick text={`sftp://${formatIp(sftp.ip)}:${sftp.port}`}>
|
||||
<Input
|
||||
type={'text'}
|
||||
value={`sftp://${sftp.ip}:${sftp.port}`}
|
||||
value={`sftp://${formatIp(sftp.ip)}:${sftp.port}`}
|
||||
readOnly
|
||||
/>
|
||||
</CopyOnClick>
|
||||
@ -59,7 +60,7 @@ export default () => {
|
||||
<div css={tw`ml-4`}>
|
||||
<LinkButton
|
||||
isSecondary
|
||||
href={`sftp://${username}.${id}@${sftp.ip}:${sftp.port}`}
|
||||
href={`sftp://${username}.${id}@${formatIp(sftp.ip)}:${sftp.port}`}
|
||||
>
|
||||
Launch SFTP
|
||||
</LinkButton>
|
||||
|
@ -63,3 +63,7 @@ export function encodePathSegments (path: string): string {
|
||||
export function hashToPath (hash: string): string {
|
||||
return hash.length > 0 ? decodeURIComponent(hash.substr(1)) : '/';
|
||||
}
|
||||
|
||||
export function formatIp (ip: string): string {
|
||||
return /([a-f0-9:]+:+)+[a-f0-9]+/.test(ip) ? `[${ip}]` : ip;
|
||||
}
|
||||
|
@ -10,13 +10,7 @@ export interface Mode {
|
||||
|
||||
const modes: Mode[] = [
|
||||
{ name: 'C', mime: 'text/x-csrc', mode: 'clike', ext: [ 'c', 'h', 'ino' ] },
|
||||
{
|
||||
name: 'C++',
|
||||
mime: 'text/x-c++src',
|
||||
mode: 'clike',
|
||||
ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ],
|
||||
alias: [ 'cpp' ],
|
||||
},
|
||||
{ name: 'C++', mime: 'text/x-c++src', mode: 'clike', ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] },
|
||||
{ name: 'C#', mime: 'text/x-csharp', mode: 'clike', ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] },
|
||||
{ name: 'CSS', mime: 'text/css', mode: 'css', ext: [ 'css' ] },
|
||||
{ name: 'CQL', mime: 'text/x-cassandra', mode: 'sql', ext: [ 'cql' ] },
|
||||
@ -24,99 +18,34 @@ const modes: Mode[] = [
|
||||
{ name: 'Dockerfile', mime: 'text/x-dockerfile', mode: 'dockerfile', file: /^Dockerfile$/ },
|
||||
{ name: 'Git Markdown', mime: 'text/x-gfm', mode: 'gfm', file: /^(readme|contributing|history|license).md$/i },
|
||||
{ name: 'Golang', mime: 'text/x-go', mode: 'go', ext: [ 'go' ] },
|
||||
{
|
||||
name: 'HTML',
|
||||
mime: 'text/html',
|
||||
mode: 'htmlmixed',
|
||||
ext: [ 'html', 'htm', 'handlebars', 'hbs' ],
|
||||
alias: [ 'xhtml' ],
|
||||
},
|
||||
{ name: 'HTML', mime: 'text/html', mode: 'htmlmixed', ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] },
|
||||
{ name: 'HTTP', mime: 'message/http', mode: 'http' },
|
||||
{
|
||||
name: 'JavaScript',
|
||||
mime: 'text/javascript',
|
||||
mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ],
|
||||
mode: 'javascript',
|
||||
ext: [ 'js' ],
|
||||
alias: [ 'ecmascript', 'js', 'node' ],
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
mime: 'application/json',
|
||||
mimes: [ 'application/json', 'application/x-json' ],
|
||||
mode: 'javascript',
|
||||
ext: [ 'json', 'map' ],
|
||||
alias: [ 'json5' ],
|
||||
},
|
||||
{ name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: 'javascript', ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] },
|
||||
{ name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: 'javascript', ext: [ 'json', 'map' ], alias: [ 'json5' ] },
|
||||
{ name: 'Lua', mime: 'text/x-lua', mode: 'lua', ext: [ 'lua' ] },
|
||||
{ name: 'Markdown', mime: 'text/x-markdown', mode: 'markdown', ext: [ 'markdown', 'md', 'mkd' ] },
|
||||
{ name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql' },
|
||||
{ name: 'MS SQL', mime: 'text/x-mssql', mode: 'sql' },
|
||||
{ name: 'MySQL', mime: 'text/x-mysql', mode: 'sql' },
|
||||
{ name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx', file: /nginx.*\.conf$/i },
|
||||
{
|
||||
name: 'PHP',
|
||||
mime: 'text/x-php',
|
||||
mimes: [ 'text/x-php', 'application/x-httpd-php', 'application/x-httpd-php-open' ],
|
||||
mode: 'php',
|
||||
ext: [ 'php', 'php3', 'php4', 'php5', 'php7', 'phtml' ],
|
||||
},
|
||||
{ name: 'PHP', mime: 'text/x-php', mimes: [ 'text/x-php', 'application/x-httpd-php', 'application/x-httpd-php-open' ], mode: 'php', ext: [ 'php', 'php3', 'php4', 'php5', 'php7', 'phtml' ] },
|
||||
{ name: 'Plain Text', mime: 'text/plain', mode: 'null', ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] },
|
||||
{ name: 'PostgreSQL', mime: 'text/x-pgsql', mode: 'sql' },
|
||||
{
|
||||
name: 'Properties',
|
||||
mime: 'text/x-properties',
|
||||
mode: 'properties',
|
||||
ext: [ 'properties', 'ini', 'in' ],
|
||||
alias: [ 'ini', 'properties' ],
|
||||
},
|
||||
{
|
||||
name: 'Python',
|
||||
mime: 'text/x-python',
|
||||
mode: 'python',
|
||||
ext: [ 'BUILD', 'bzl', 'py', 'pyw' ],
|
||||
file: /^(BUCK|BUILD)$/,
|
||||
},
|
||||
{
|
||||
name: 'Ruby',
|
||||
mime: 'text/x-ruby',
|
||||
mode: 'ruby',
|
||||
ext: [ 'rb' ],
|
||||
alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ],
|
||||
},
|
||||
{ name: 'Properties', mime: 'text/x-properties', mode: 'properties', ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] },
|
||||
{ name: 'Pug', mime: 'text/x-pug', mimes: [ 'text/x-pug', 'text/x-jade' ], mode: 'null', ext: [ 'pug' ] },
|
||||
{ name: 'Python', mime: 'text/x-python', mode: 'python', ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ },
|
||||
{ name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby', ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] },
|
||||
{ name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust', ext: [ 'rs' ] },
|
||||
{ name: 'Sass', mime: 'text/x-sass', mode: 'sass', ext: [ 'sass' ] },
|
||||
{ name: 'SCSS', mime: 'text/x-scss', mode: 'css', ext: [ 'scss' ] },
|
||||
{
|
||||
name: 'Shell',
|
||||
mime: 'text/x-sh',
|
||||
mimes: [ 'text/x-sh', 'application/x-sh' ],
|
||||
mode: 'shell',
|
||||
ext: [ 'sh', 'ksh', 'bash' ],
|
||||
alias: [ 'bash', 'sh', 'zsh' ],
|
||||
file: /^PKGBUILD$/,
|
||||
},
|
||||
{ name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: 'shell', ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ },
|
||||
{ name: 'SQL', mime: 'text/x-sql', mode: 'sql', ext: [ 'sql' ] },
|
||||
{ name: 'SQLite', mime: 'text/x-sqlite', mode: 'sql' },
|
||||
{ name: 'TOML', mime: 'text/x-toml', mode: 'toml', ext: [ 'toml' ] },
|
||||
{ name: 'TypeScript', mime: 'application/typescript', mode: 'javascript', ext: [ 'ts' ], alias: [ 'ts' ] },
|
||||
{ name: 'Vue', mime: 'script/x-vue', mimes: [ 'script/x-vue', 'text/x-vue' ], mode: 'vue', ext: [ 'vue' ] },
|
||||
{
|
||||
name: 'XML',
|
||||
mime: 'application/xml',
|
||||
mimes: [ 'application/xml', 'text/xml' ],
|
||||
mode: 'xml',
|
||||
ext: [ 'xml', 'xsl', 'xsd', 'svg' ],
|
||||
alias: [ 'rss', 'wsdl', 'xsd' ],
|
||||
},
|
||||
{
|
||||
name: 'YAML',
|
||||
mime: 'text/x-yaml',
|
||||
mimes: [ 'text/x-yaml', 'text/yaml' ],
|
||||
mode: 'yaml',
|
||||
ext: [ 'yaml', 'yml' ],
|
||||
alias: [ 'yml' ],
|
||||
},
|
||||
{ name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: 'xml', ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] },
|
||||
{ name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: 'yaml', ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] },
|
||||
];
|
||||
|
||||
export default modes;
|
||||
|
@ -70,7 +70,11 @@
|
||||
@parent
|
||||
<script>
|
||||
$('#configTokenBtn').on('click', function (event) {
|
||||
$.getJSON('{{ route('admin.nodes.view.configuration.token', $node->id) }}').done(function (data) {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '{{ route('admin.nodes.view.configuration.token', $node->id) }}',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
}).done(function (data) {
|
||||
swal({
|
||||
type: 'success',
|
||||
title: 'Token created.',
|
||||
|
@ -145,9 +145,9 @@
|
||||
showLoaderOnConfirm: true
|
||||
}, function () {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
url: '/admin/settings/mail/test',
|
||||
headers: { 'X-CSRF-Token': $('input[name="_token"]').val() }
|
||||
headers: { 'X-CSRF-TOKEN': $('input[name="_token"]').val() }
|
||||
}).fail(function (jqXHR) {
|
||||
showErrorDialog(jqXHR, 'test');
|
||||
}).done(function () {
|
||||
|
@ -66,8 +66,8 @@ Route::group(['prefix' => 'databases'], function () {
|
||||
Route::group(['prefix' => 'settings'], function () {
|
||||
Route::get('/', 'Settings\IndexController@index')->name('admin.settings');
|
||||
Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail');
|
||||
Route::get('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test');
|
||||
Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced');
|
||||
Route::post('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test');
|
||||
|
||||
Route::patch('/', 'Settings\IndexController@update');
|
||||
Route::patch('/mail', 'Settings\MailController@update');
|
||||
@ -153,12 +153,12 @@ Route::group(['prefix' => 'nodes'], function () {
|
||||
Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation');
|
||||
Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers');
|
||||
Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController');
|
||||
Route::get('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token');
|
||||
|
||||
Route::post('/new', 'NodesController@store');
|
||||
Route::post('/view/{node}/allocation', 'NodesController@createAllocation');
|
||||
Route::post('/view/{node}/allocation/remove', 'NodesController@allocationRemoveBlock')->name('admin.nodes.view.allocation.removeBlock');
|
||||
Route::post('/view/{node}/allocation/alias', 'NodesController@allocationSetAlias')->name('admin.nodes.view.allocation.setAlias');
|
||||
Route::post('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token');
|
||||
|
||||
Route::patch('/view/{node}/settings', 'NodesController@updateSettings');
|
||||
|
||||
|
@ -5,6 +5,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Location;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\Location;
|
||||
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
||||
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
|
||||
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
|
||||
use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase;
|
||||
@ -88,6 +89,77 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a location can be created.
|
||||
*/
|
||||
public function testCreateLocation()
|
||||
{
|
||||
$response = $this->postJson('/api/application/locations', [
|
||||
'short' => 'inhouse',
|
||||
'long' => 'This is my inhouse location',
|
||||
]);
|
||||
|
||||
$response->assertStatus(Response::HTTP_CREATED);
|
||||
$response->assertJsonCount(3);
|
||||
$response->assertJsonStructure([
|
||||
'object',
|
||||
'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'],
|
||||
'meta' => ['resource'],
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('locations', ['short' => 'inhouse', 'long' => 'This is my inhouse location']);
|
||||
|
||||
$location = Location::where('short', 'inhouse')->first();
|
||||
$response->assertJson([
|
||||
'object' => 'location',
|
||||
'attributes' => $this->getTransformer(LocationTransformer::class)->transform($location),
|
||||
'meta' => [
|
||||
'resource' => route('api.application.locations.view', $location->id),
|
||||
],
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a location can be updated.
|
||||
*/
|
||||
public function testUpdateLocation()
|
||||
{
|
||||
$location = Location::factory()->create();
|
||||
|
||||
$response = $this->patchJson('/api/application/locations/' . $location->id, [
|
||||
'short' => 'new inhouse',
|
||||
'long' => 'This is my new inhouse location'
|
||||
]);
|
||||
$response->assertStatus(Response::HTTP_OK);
|
||||
$response->assertJsonCount(2);
|
||||
$response->assertJsonStructure([
|
||||
'object',
|
||||
'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at']
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('locations', ['short' => 'new inhouse', 'long' => 'This is my new inhouse location']);
|
||||
$location = $location->fresh();
|
||||
|
||||
$response->assertJson([
|
||||
'object' => 'location',
|
||||
'attributes' => $this->getTransformer(LocationTransformer::class)->transform($location),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a location can be deleted from the database.
|
||||
*/
|
||||
public function testDeleteLocation()
|
||||
{
|
||||
$location = Location::factory()->create();
|
||||
$this->assertDatabaseHas('locations', ['id' => $location->id]);
|
||||
|
||||
$response = $this->delete('/api/application/locations/' . $location->id);
|
||||
$response->assertStatus(Response::HTTP_NO_CONTENT);
|
||||
|
||||
$this->assertDatabaseMissing('locations', ['id' => $location->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all of the defined relationships for a location can be loaded successfully.
|
||||
*/
|
||||
|
@ -26,7 +26,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all of the eggs belonging to a given nest can be returned.
|
||||
* Test that all the eggs belonging to a given nest can be returned.
|
||||
*/
|
||||
public function testListAllEggsInNest()
|
||||
{
|
||||
@ -47,7 +47,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase
|
||||
'files' => [],
|
||||
'startup' => ['done'],
|
||||
'stop',
|
||||
'logs' => ['custom', 'location'],
|
||||
'logs' => [],
|
||||
'extends',
|
||||
],
|
||||
],
|
||||
|
@ -1,168 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Pterodactyl\Tests\Unit\Http\Middleware\Api;
|
||||
|
||||
use Mockery as m;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Http\Middleware\Api\AuthenticateKey;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
|
||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class AuthenticateKeyTest extends MiddlewareTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Auth\AuthManager|\Mockery\Mock
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
|
||||
*/
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->auth = m::mock(AuthManager::class);
|
||||
$this->encrypter = m::mock(Encrypter::class);
|
||||
$this->repository = m::mock(ApiKeyRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a missing bearer token will throw an exception.
|
||||
*/
|
||||
public function testMissingBearerTokenThrowsException()
|
||||
{
|
||||
$this->request->shouldReceive('user')->andReturnNull();
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull();
|
||||
|
||||
try {
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
|
||||
} catch (HttpException $exception) {
|
||||
$this->assertEquals(401, $exception->getStatusCode());
|
||||
$this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an invalid API identifier throws an exception.
|
||||
*/
|
||||
public function testInvalidIdentifier()
|
||||
{
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234');
|
||||
$this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException());
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a valid token can continue past the middleware.
|
||||
*/
|
||||
public function testValidToken()
|
||||
{
|
||||
$model = ApiKey::factory()->make();
|
||||
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted');
|
||||
$this->repository->shouldReceive('findFirstWhere')->with([
|
||||
['identifier', '=', $model->identifier],
|
||||
['key_type', '=', ApiKey::TYPE_APPLICATION],
|
||||
])->once()->andReturn($model);
|
||||
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
|
||||
$this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull();
|
||||
|
||||
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
|
||||
'last_used_at' => CarbonImmutable::now(),
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
|
||||
$this->assertEquals($model, $this->request->attributes->get('api_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a valid token can continue past the middleware when set as a user token.
|
||||
*/
|
||||
public function testValidTokenWithUserKey()
|
||||
{
|
||||
$model = ApiKey::factory()->make();
|
||||
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted');
|
||||
$this->repository->shouldReceive('findFirstWhere')->with([
|
||||
['identifier', '=', $model->identifier],
|
||||
['key_type', '=', ApiKey::TYPE_ACCOUNT],
|
||||
])->once()->andReturn($model);
|
||||
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
|
||||
$this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull();
|
||||
|
||||
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
|
||||
'last_used_at' => CarbonImmutable::now(),
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT);
|
||||
$this->assertEquals($model, $this->request->attributes->get('api_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can still make it though this middleware if the user is logged in and passing
|
||||
* through a cookie.
|
||||
*/
|
||||
public function testAccessWithoutToken()
|
||||
{
|
||||
$user = User::factory()->make(['id' => 123]);
|
||||
|
||||
$this->request->shouldReceive('user')->andReturn($user);
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturnNull();
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT);
|
||||
$model = $this->request->attributes->get('api_key');
|
||||
|
||||
$this->assertSame(ApiKey::TYPE_ACCOUNT, $model->key_type);
|
||||
$this->assertSame(123, $model->user_id);
|
||||
$this->assertNull($model->identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a valid token identifier with an invalid token attached to it
|
||||
* triggers an exception.
|
||||
*/
|
||||
public function testInvalidTokenForIdentifier()
|
||||
{
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
|
||||
$model = ApiKey::factory()->make();
|
||||
|
||||
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf');
|
||||
$this->repository->shouldReceive('findFirstWhere')->with([
|
||||
['identifier', '=', $model->identifier],
|
||||
['key_type', '=', ApiKey::TYPE_APPLICATION],
|
||||
])->once()->andReturn($model);
|
||||
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the middleware with mocked dependencies for testing.
|
||||
*/
|
||||
private function getMiddleware(): AuthenticateKey
|
||||
{
|
||||
return new AuthenticateKey($this->repository, $this->auth, $this->encrypter);
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Pterodactyl\Tests\Unit\Http\Middleware\Api;
|
||||
|
||||
use Mockery as m;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Pterodactyl\Http\Middleware\Api\SetSessionDriver;
|
||||
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
|
||||
|
||||
class SetSessionDriverTest extends MiddlewareTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->config = m::mock(Repository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a production environment does not try to disable debug bar.
|
||||
*/
|
||||
public function testMiddleware()
|
||||
{
|
||||
$this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull();
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the middleware with mocked dependencies for testing.
|
||||
*/
|
||||
private function getMiddleware(): SetSessionDriver
|
||||
{
|
||||
return new SetSessionDriver($this->config);
|
||||
}
|
||||
}
|
@ -8040,10 +8040,10 @@ xterm-addon-web-links@^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.12.0:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0.tgz#db09b425b4dcae5b96f8cbbaaa93b3bc60997ca9"
|
||||
integrity sha512-K5mF/p3txUV18mjiZFlElagoHFpqXrm5OYHeoymeXSu8GG/nMaOO/+NRcNCwfdjzAbdQ5VLF32hEHiWWKKm0bw==
|
||||
xterm@^4.15.0:
|
||||
version "4.15.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0.tgz#e52038507eba7e0d36d47f81e29fe548c82b9561"
|
||||
integrity sha512-Ik1GoSq1yqKZQ2LF37RPS01kX9t4TP8gpamUYblD09yvWX5mEYuMK4CcqH6+plgiNEZduhTz/UrcaWs97gOlOw==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user