Merge branch 'develop' into v2

This commit is contained in:
Matthew Penner 2021-08-03 14:41:12 -06:00
commit a39802cb4e
19 changed files with 241 additions and 99 deletions

View file

@ -3,6 +3,27 @@ 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. This project follows [Semantic Versioning](http://semver.org) guidelines.
## v1.5.0
### Fixed
* Fixes deleting a locked backup that has also been marked as failed to allow deletion rather than returning an error about being locked.
* Fixes server creation process not correctly sending `start_on_completion` to Wings instance.
* Fixes `z-index` on file mass delete modal so it is displayed on top of all elements, rather than hidden under some.
* Supports re-sending requests to the Panel API for backups that are currently marked as failed, allowing a previously failed backup to be marked as successful.
* Minor updates to multiple default eggs for improved error handling and more accurate field-level validation.
### Updated
* Updates help text for CPU limiting when creating a new server to properly indicate virtual threads are included, rather than only physical threads.
* Updates all of the default eggs shipped with the Panel to reference new [`ghcr.io` yolks repository](https://github.com/pterodactyl/yolks).
* When adding 2FA to an account the key used to generate the token is now displayed to the user allowing them to manually input into their app if necessary.
### Added
* Adds SSL/TLS options for MySQL and Redis in line with most recent Laravel updates.
* New users created for server MySQL instances will now have the correct permissions for creating foreign keys on tables.
* Adds new automatic popup feature to allow users to quickly update their Minecraft servers to the latest Java® eggs as necessary if unsupported versions are detected.
### Removed
* Removes legacy `userInteraction` key from eggs which was unused.
## v1.4.2 ## v1.4.2
### Fixed ### Fixed
* Fixes logic to disallow creating a backup schedule if the server's backup limit is set to 0. * Fixes logic to disallow creating a backup schedule if the server's backup limit is set to 0.

View file

@ -35,6 +35,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
| [**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/?utm_source=pterodactyl) | 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*! |
| [**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. | | [**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.|
## Documentation ## Documentation
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
@ -65,7 +66,6 @@ and there are plenty more games available provided by the community. Some of the
* San Andreas: MP * San Andreas: MP
* Pocketmine MP * Pocketmine MP
* Squad * Squad
* FiveM
* Xonotic * Xonotic
* Starmade * Starmade
* Discord ATLBot, and most other Node.js/Python discord bots * Discord ATLBot, and most other Node.js/Python discord bots

View file

@ -5,8 +5,10 @@ The following versions of Pterodactyl are receiving active support and maintenan
| Panel | Daemon | Supported | | Panel | Daemon | Supported |
| ----- | ------------ | ------------------ | | ----- | ------------ | ------------------ |
| 1.2.x | wings@1.2.x | :white_check_mark: | | 1.4.x | wings@1.4.x | :white_check_mark: |
| 1.1.x | wings@1.1.x | :white_check_mark: | | 1.3.x | wings@1.3.x | :x: |
| 1.2.x | wings@1.2.x | :x: |
| 1.1.x | wings@1.1.x | :x: |
| 1.0.x | wings@1.0.x | :x: | | 1.0.x | wings@1.0.x | :x: |
| 0.7.x | daemon@0.6.x | :x: | | 0.7.x | daemon@0.6.x | :x: |
| 0.6.x | daemon@0.5.x | :x: | | 0.6.x | daemon@0.5.x | :x: |

View file

@ -48,9 +48,7 @@ class TwoFactorController extends ClientApiController
} }
return new JsonResponse([ return new JsonResponse([
'data' => [ 'data' => $this->setupService->handle($request->user()),
'image_url_data' => $this->setupService->handle($request->user()),
],
]); ]);
} }

View file

@ -49,7 +49,7 @@ class TwoFactorSetupService
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function handle(User $user): string public function handle(User $user): array
{ {
$secret = ''; $secret = '';
try { try {
@ -66,11 +66,14 @@ class TwoFactorSetupService
$company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name'))); $company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name')));
return sprintf( return [
'image_url_data' => sprintf(
'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s',
rawurlencode($company), rawurlencode($company),
rawurlencode($user->email), rawurlencode($user->email),
rawurlencode($secret) rawurlencode($secret),
); ),
'secret' => $secret,
];
} }
} }

View file

@ -4,7 +4,7 @@
"version": "PTDL_v1", "version": "PTDL_v1",
"update_url": null "update_url": null
}, },
"exported_at": "2021-07-24T11:38:02+03:00", "exported_at": "2021-08-01T03:54:45+03:00",
"name": "Paper", "name": "Paper",
"author": "parker@pterodactyl.io", "author": "parker@pterodactyl.io",
"description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.", "description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.",
@ -38,7 +38,7 @@
"env_variable": "MINECRAFT_VERSION", "env_variable": "MINECRAFT_VERSION",
"default_value": "latest", "default_value": "latest",
"user_viewable": true, "user_viewable": true,
"user_editable": false, "user_editable": true,
"rules": "nullable|string|max:20" "rules": "nullable|string|max:20"
}, },
{ {
@ -65,7 +65,7 @@
"env_variable": "BUILD_NUMBER", "env_variable": "BUILD_NUMBER",
"default_value": "latest", "default_value": "latest",
"user_viewable": true, "user_viewable": true,
"user_editable": false, "user_editable": true,
"rules": "required|string|max:20" "rules": "required|string|max:20"
} }
] ]

View file

@ -4,7 +4,7 @@
"version": "PTDL_v1", "version": "PTDL_v1",
"update_url": null "update_url": null
}, },
"exported_at": "2021-07-04T19:19:11-04:00", "exported_at": "2021-08-01T03:55:24+03:00",
"name": "Sponge (SpongeVanilla)", "name": "Sponge (SpongeVanilla)",
"author": "support@pterodactyl.io", "author": "support@pterodactyl.io",
"description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.", "description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.",
@ -21,7 +21,7 @@
"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}",
"startup": "{\r\n \"done\": \")! For help, type \"\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" "stop": "stop"
}, },
"scripts": { "scripts": {
@ -38,7 +38,7 @@
"env_variable": "SPONGE_VERSION", "env_variable": "SPONGE_VERSION",
"default_value": "1.12.2-7.3.0", "default_value": "1.12.2-7.3.0",
"user_viewable": true, "user_viewable": true,
"user_editable": false, "user_editable": true,
"rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/" "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/"
}, },
{ {

View file

@ -4,7 +4,7 @@
"version": "PTDL_v1", "version": "PTDL_v1",
"update_url": null "update_url": null
}, },
"exported_at": "2021-06-10T00:43:46+03:00", "exported_at": "2021-07-27T14:14:20+03:00",
"name": "Ark: Survival Evolved", "name": "Ark: Survival Evolved",
"author": "dev@shepper.fr", "author": "dev@shepper.fr",
"description": "As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! \u2014 Gamepedia: ARK", "description": "As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! \u2014 Gamepedia: ARK",
@ -13,11 +13,11 @@
"quay.io\/parkervcp\/pterodactyl-images:debian_source" "quay.io\/parkervcp\/pterodactyl-images:debian_source"
], ],
"file_denylist": [], "file_denylist": [],
"startup": "rmv() { echo -e \"stopping server\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c saveworld && rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c DoExit; }; trap rmv 15; cd ShooterGame\/Binaries\/Linux && .\/ShooterGameServer {{SERVER_MAP}}?listen?SessionName=\"{{SESSION_NAME}}\"?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?RCONPort={{RCON_PORT}}?QueryPort={{QUERY_PORT}}?RCONEnabled={{ENABLE_RCON}}$( [ \"$BATTLE_EYE\" == \"0\" ] || printf %s '?-NoBattlEye' ) -server -log & until echo \"waiting for rcon connection...\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD}; do sleep 5; done", "startup": "rmv() { echo -e \"stopping server\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c saveworld && rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD} -c DoExit; }; trap rmv 15; cd ShooterGame\/Binaries\/Linux && .\/ShooterGameServer {{SERVER_MAP}}?listen?SessionName=\"{{SESSION_NAME}}\"?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?RCONPort={{RCON_PORT}}?QueryPort={{QUERY_PORT}}?RCONEnabled=True$( [ \"$BATTLE_EYE\" == \"0\" ] || printf %s '?-NoBattlEye' ) -server {{ARGS}} -log & until echo \"waiting for rcon connection...\"; rcon -t rcon -a 127.0.0.1:${RCON_PORT} -p ${ARK_ADMIN_PASSWORD}; do sleep 5; done",
"config": { "config": {
"files": "{}", "files": "{}",
"startup": "{\r\n \"done\": \"Waiting commands for 127.0.0.1:\",\r\n \"userInteraction\": []\r\n}", "startup": "{\r\n \"done\": \"Waiting commands for 127.0.0.1:\"\r\n}",
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", "logs": "{}",
"stop": "^C" "stop": "^C"
}, },
"scripts": { "scripts": {
@ -44,7 +44,7 @@
"default_value": "PleaseChangeMe", "default_value": "PleaseChangeMe",
"user_viewable": true, "user_viewable": true,
"user_editable": true, "user_editable": true,
"rules": "nullable|alpha_dash|between:1,100" "rules": "required|alpha_dash|between:1,100"
}, },
{ {
"name": "Server Map", "name": "Server Map",
@ -55,15 +55,6 @@
"user_editable": true, "user_editable": true,
"rules": "required|string|max:20" "rules": "required|string|max:20"
}, },
{
"name": "App ID",
"description": "ARK steam app id for auto updates. Leave blank to avoid auto update.",
"env_variable": "SRCDS_APPID",
"default_value": "376030",
"user_viewable": true,
"user_editable": false,
"rules": "nullable|numeric"
},
{ {
"name": "Server Name", "name": "Server Name",
"description": "ARK server name", "description": "ARK server name",
@ -73,15 +64,6 @@
"user_editable": true, "user_editable": true,
"rules": "required|string|max:128" "rules": "required|string|max:128"
}, },
{
"name": "Use Rcon",
"description": "Enable or disable rcon system. (true or false)\r\n\r\nDefault True for the console to work.",
"env_variable": "ENABLE_RCON",
"default_value": "True",
"user_viewable": true,
"user_editable": false,
"rules": "required|string|in:True,False"
},
{ {
"name": "Rcon Port", "name": "Rcon Port",
"description": "ARK rcon port used by rcon tools.", "description": "ARK rcon port used by rcon tools.",
@ -117,6 +99,24 @@
"user_viewable": true, "user_viewable": true,
"user_editable": true, "user_editable": true,
"rules": "required|boolean" "rules": "required|boolean"
},
{
"name": "App ID",
"description": "ARK steam app id for auto updates. Leave blank to avoid auto update.",
"env_variable": "SRCDS_APPID",
"default_value": "376030",
"user_viewable": true,
"user_editable": false,
"rules": "nullable|numeric"
},
{
"name": "Additional Arguments",
"description": "Specify additional launch parameters such as -crossplay. You must include a dash - and separate each parameter with space: -crossplay -exclusivejoin",
"env_variable": "ARGS",
"default_value": "",
"user_viewable": true,
"user_editable": true,
"rules": "nullable|string"
} }
] ]
} }

View file

@ -61,8 +61,7 @@ services:
- "/srv/pterodactyl/certs/:/etc/letsencrypt/" - "/srv/pterodactyl/certs/:/etc/letsencrypt/"
- "/srv/pterodactyl/logs/:/app/storage/logs" - "/srv/pterodactyl/logs/:/app/storage/logs"
environment: environment:
<<: *panel-environment <<: [*panel-environment, *mail-environment]
<<: *mail-environment
DB_PASSWORD: *db-password DB_PASSWORD: *db-password
APP_ENV: "production" APP_ENV: "production"
APP_ENVIRONMENT_ONLY: "false" APP_ENVIRONMENT_ONLY: "false"

View file

@ -0,0 +1,15 @@
import http from '@/api/http';
export interface TwoFactorTokenData {
// eslint-disable-next-line camelcase
image_url_data: string;
secret: string;
}
export default (): Promise<TwoFactorTokenData> => {
return new Promise((resolve, reject) => {
http.get('/api/client/account/two-factor')
.then(({ data }) => resolve(data.data))
.catch(reject);
});
};

View file

@ -1,9 +0,0 @@
import http from '@/api/http';
export default (): Promise<string> => {
return new Promise((resolve, reject) => {
http.get('/api/client/account/two-factor')
.then(({ data }) => resolve(data.data.image_url_data))
.catch(reject);
});
};

View file

@ -43,7 +43,7 @@ const DisableTwoFactorModal = () => {
password: '', password: '',
}} }}
validationSchema={object().shape({ validationSchema={object().shape({
password: string().required('You must provider your current password in order to continue.'), password: string().required('You must provide your current password in order to continue.'),
})} })}
> >
{({ isValid }) => ( {({ isValid }) => (

View file

@ -1,7 +1,7 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup'; import { object, string } from 'yup';
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
import { Actions, useStoreActions } from 'easy-peasy'; import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
@ -12,13 +12,14 @@ import Button from '@/components/elements/Button';
import asModal from '@/hoc/asModal'; import asModal from '@/hoc/asModal';
import ModalContext from '@/context/ModalContext'; import ModalContext from '@/context/ModalContext';
import QRCode from 'qrcode.react'; import QRCode from 'qrcode.react';
import CopyOnClick from '@/components/elements/CopyOnClick';
interface Values { interface Values {
code: string; code: string;
} }
const SetupTwoFactorModal = () => { const SetupTwoFactorModal = () => {
const [ token, setToken ] = useState(''); const [ token, setToken ] = useState<TwoFactorTokenData | null>(null);
const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]); const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]);
const { dismiss, setPropOverrides } = useContext(ModalContext); const { dismiss, setPropOverrides } = useContext(ModalContext);
@ -26,7 +27,7 @@ const SetupTwoFactorModal = () => {
const { clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes); const { clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
useEffect(() => { useEffect(() => {
getTwoFactorTokenUrl() getTwoFactorTokenData()
.then(setToken) .then(setToken)
.catch(error => { .catch(error => {
console.error(error); console.error(error);
@ -102,13 +103,17 @@ const SetupTwoFactorModal = () => {
<div css={tw`flex flex-wrap`}> <div css={tw`flex flex-wrap`}>
<div css={tw`w-full md:flex-1`}> <div css={tw`w-full md:flex-1`}>
<div css={tw`w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto`}> <div css={tw`w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto`}>
{!token || !token.length ? {!token ?
<img <img
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='} src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
css={tw`w-64 h-64 rounded`} css={tw`w-64 h-64 rounded`}
/> />
: :
<QRCode renderAs={'svg'} value={token} css={tw`w-full h-full shadow-none rounded-none`}/> <QRCode
renderAs={'svg'}
value={token.image_url_data}
css={tw`w-full h-full shadow-none rounded-none`}
/>
} }
</div> </div>
</div> </div>
@ -121,11 +126,21 @@ const SetupTwoFactorModal = () => {
title={'Code From Authenticator'} title={'Code From Authenticator'}
description={'Enter the code from your authenticator device after scanning the QR image.'} description={'Enter the code from your authenticator device after scanning the QR image.'}
/> />
{token &&
<div css={tw`mt-4 pt-4 border-t border-neutral-500 text-neutral-200`}>
Alternatively, enter the following token into your authenticator application:
<CopyOnClick text={token.secret}>
<div css={tw`text-sm bg-neutral-900 rounded mt-2 py-2 px-4 font-mono`}>
<code css={tw`font-mono`}>
{token.secret}
</code>
</div>
</CopyOnClick>
</div>
}
</div> </div>
<div css={tw`mt-6 md:mt-0 text-right`}> <div css={tw`mt-6 md:mt-0 text-right`}>
<Button> <Button>Setup</Button>
Setup
</Button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -7,7 +7,7 @@ import ServerContentBlock from '@/components/elements/ServerContentBlock';
import ServerDetailsBlock from '@/components/server/ServerDetailsBlock'; import ServerDetailsBlock from '@/components/server/ServerDetailsBlock';
import isEqual from 'react-fast-compare'; import isEqual from 'react-fast-compare';
import PowerControls from '@/components/server/PowerControls'; import PowerControls from '@/components/server/PowerControls';
import { EulaModalFeature } from 'feature/index'; import { EulaModalFeature, JavaVersionModalFeature } from 'feature/index';
import ErrorBoundary from '@/components/elements/ErrorBoundary'; import ErrorBoundary from '@/components/elements/ErrorBoundary';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
@ -60,6 +60,7 @@ const ServerConsole = () => {
{eggFeatures.includes('eula') && {eggFeatures.includes('eula') &&
<React.Suspense fallback={null}> <React.Suspense fallback={null}>
<EulaModalFeature/> <EulaModalFeature/>
<JavaVersionModalFeature/>
</React.Suspense> </React.Suspense>
} }
</div> </div>

View file

@ -0,0 +1,105 @@
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 setSelectedDockerImage from '@/api/server/setSelectedDockerImage';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
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 16', image: 'ghcr.io/pterodactyl/yolks:java_16' },
];
const JavaVersionModalFeature = () => {
const [ visible, setVisible ] = useState(false);
const [ loading, setLoading ] = useState(false);
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_16');
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 = [
'minecraft 1.17 requires running the server with java 16 or above',
'java.lang.unsupportedclassversionerror',
'unsupported major.minor version',
'has been compiled by a more recent version of the java runtime',
];
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 updateJava = () => {
setLoading(true);
clearFlashes('feature:javaVersion');
setSelectedDockerImage(uuid, selectedVersion)
.then(() => {
if (status === 'offline' && instance) {
instance.send(SocketRequest.SET_STATE, 'restart');
}
setLoading(false);
setVisible(false);
})
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'feature:javaVersion', error });
})
.then(() => setLoading(false));
};
useEffect(() => {
clearFlashes('feature:javaVersion');
}, []);
return (
<Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
<FlashMessageRender key={'feature:javaVersion'} css={tw`mb-4`}/>
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Invalid Java Version, Update Docker Image?</h2>
<p css={tw`mt-4`}>This server is unable to start due to the required java version not being met.</p>
<p css={tw`mt-4`}>By pressing {'"Update Docker Image"'} below you are acknowledging that the docker image this server uses will be changed to a image below that has the Java version you are requesting.</p>
<div css={tw`sm:flex items-center mt-4`}>
<p>Please select a Java version from the list below.</p>
<Select
onChange={e => setSelectedVersion(e.target.value)}
>
{dockerImageList.map((key, index) => {
return (
<option key={index} value={key.image}>{key.name}</option>
);
})}
</Select>
</div>
<div css={tw`mt-8 sm:flex items-center justify-end`}>
<Button isSecondary onClick={() => setVisible(false)} css={tw`w-full sm:w-auto border-transparent`}>
Cancel
</Button>
<Button onClick={updateJava} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
Update Docker Image
</Button>
</div>
</Modal>
);
};
export default JavaVersionModalFeature;

View file

@ -53,15 +53,12 @@ const EulaModalFeature = () => {
.then(() => setLoading(false)); .then(() => setLoading(false));
}; };
useEffect(() => () => { useEffect(() => {
clearFlashes('feature:eula'); clearFlashes('feature:eula');
}, []); }, []);
return ( return (
!visible ? <Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
null
:
<Modal visible onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
<FlashMessageRender key={'feature:eula'} css={tw`mb-4`}/> <FlashMessageRender key={'feature:eula'} css={tw`mb-4`}/>
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft&reg; EULA</h2> <h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft&reg; EULA</h2>
<p css={tw`text-neutral-200`}> <p css={tw`text-neutral-200`}>

View file

@ -7,5 +7,6 @@ import { lazy } from 'react';
* on the feature and the egg). * on the feature and the egg).
*/ */
const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'feature/eula/EulaModalFeature')); const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'feature/eula/EulaModalFeature'));
const JavaVersionModalFeature = lazy(() => import(/* webpackChunkName: "feature.java_version" */'feature/JavaVersionModalFeature'));
export { EulaModalFeature }; export { EulaModalFeature, JavaVersionModalFeature };

View file

@ -58,7 +58,7 @@ export default () => {
css={tw`cursor-pointer mb-2 flex-wrap`} css={tw`cursor-pointer mb-2 flex-wrap`}
onClick={(e: any) => { onClick={(e: any) => {
e.preventDefault(); e.preventDefault();
history.push(`${match.url}/${schedule.id}`, { schedule }); history.push(`${match.url}/${schedule.id}`);
}} }}
> >
<ScheduleRow schedule={schedule}/> <ScheduleRow schedule={schedule}/>

View file

@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { Schedule } from '@/api/server/schedules/getServerSchedules';
import getServerSchedule from '@/api/server/schedules/getServerSchedule'; import getServerSchedule from '@/api/server/schedules/getServerSchedule';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
@ -23,10 +22,6 @@ interface Params {
id: string; id: string;
} }
interface State {
schedule?: Schedule;
}
const CronBox = ({ title, value }: { title: string; value: string }) => ( const CronBox = ({ title, value }: { title: string; value: string }) => (
<div css={tw`bg-neutral-700 rounded p-3`}> <div css={tw`bg-neutral-700 rounded p-3`}>
<p css={tw`text-neutral-300 text-sm`}>{title}</p> <p css={tw`text-neutral-300 text-sm`}>{title}</p>
@ -47,7 +42,6 @@ const ActivePill = ({ active }: { active: boolean }) => (
export default () => { export default () => {
const history = useHistory(); const history = useHistory();
const { state } = useLocation<State>();
const { id: scheduleId } = useParams<Params>(); const { id: scheduleId } = useParams<Params>();
const id = ServerContext.useStoreState(state => state.server.data!.id); const id = ServerContext.useStoreState(state => state.server.data!.id);
@ -57,7 +51,7 @@ export default () => {
const [ isLoading, setIsLoading ] = useState(true); const [ isLoading, setIsLoading ] = useState(true);
const [ showEditModal, setShowEditModal ] = useState(false); const [ showEditModal, setShowEditModal ] = useState(false);
const schedule = ServerContext.useStoreState(st => st.schedules.data.find(s => s.id === state.schedule?.id), isEqual); const schedule = ServerContext.useStoreState(st => st.schedules.data.find(s => s.id === Number(scheduleId)), isEqual);
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
useEffect(() => { useEffect(() => {