From 924f00ac9aa20a0b100996655a5b61b00aa15d55 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Mon, 2 Aug 2021 23:32:10 -0400 Subject: [PATCH 1/2] Java Docker image updater feature (#3430) Co-Authored-By: Lance Pioch --- .../components/server/ServerConsole.tsx | 3 +- .../features/JavaVersionModalFeature.tsx | 105 ++++++++++++++++++ .../server/features/eula/EulaModalFeature.tsx | 43 ++++--- .../components/server/features/index.ts | 3 +- 4 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 resources/scripts/components/server/features/JavaVersionModalFeature.tsx diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 48385d7c0..95d595add 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -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 } from '@feature/index'; +import { EulaModalFeature, JavaVersionModalFeature } from '@feature/index'; import ErrorBoundary from '@/components/elements/ErrorBoundary'; import Spinner from '@/components/elements/Spinner'; @@ -60,6 +60,7 @@ const ServerConsole = () => { {eggFeatures.includes('eula') && + } diff --git a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx new file mode 100644 index 000000000..fb81c07a4 --- /dev/null +++ b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx @@ -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 ( + setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}> + +

Invalid Java Version, Update Docker Image?

+

This server is unable to start due to the required java version not being met.

+

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.

+
+

Please select a Java version from the list below.

+ +
+
+ + +
+
+ ); +}; + +export default JavaVersionModalFeature; diff --git a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx index 586b472db..b1f26f22e 100644 --- a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx +++ b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx @@ -53,37 +53,34 @@ const EulaModalFeature = () => { .then(() => setLoading(false)); }; - useEffect(() => () => { + useEffect(() => { clearFlashes('feature:eula'); }, []); return ( - !visible ? - null - : - setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}> - -

Accept Minecraft® EULA

-

+ setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}> + +

Accept Minecraft® EULA

+

By pressing {'"I Accept"'} below you are indicating your agreement to the  - + Minecraft® EULA - . -

-
- - + -
-
+ + + ); }; diff --git a/resources/scripts/components/server/features/index.ts b/resources/scripts/components/server/features/index.ts index 2c285feb6..9ffe01188 100644 --- a/resources/scripts/components/server/features/index.ts +++ b/resources/scripts/components/server/features/index.ts @@ -7,5 +7,6 @@ import { lazy } from 'react'; * on the feature and the egg). */ const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'@feature/eula/EulaModalFeature')); +const JavaVersionModalFeature = lazy(() => import(/* webpackChunkName: "feature.javaVersion" */'@feature/JavaVersionModalFeature')); -export { EulaModalFeature }; +export { EulaModalFeature, JavaVersionModalFeature }; From bda1ff50ab71cc1bd7ed0e9bcaf3637fb18897b1 Mon Sep 17 00:00:00 2001 From: Mia Date: Tue, 3 Aug 2021 05:39:12 +0200 Subject: [PATCH 2/2] [UI] Display the 2FA token, show spinner on load (#3367) Co-authored-by: Dane Everitt --- .../Api/Client/TwoFactorController.php | 4 +-- app/Services/Users/TwoFactorSetupService.php | 17 +++++----- .../api/account/getTwoFactorTokenData.ts | 15 +++++++++ .../api/account/getTwoFactorTokenUrl.ts | 9 ------ .../dashboard/forms/SetupTwoFactorModal.tsx | 31 ++++++++++++++----- 5 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 resources/scripts/api/account/getTwoFactorTokenData.ts delete mode 100644 resources/scripts/api/account/getTwoFactorTokenUrl.ts diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 07eeb2972..b14f9d4bc 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -61,9 +61,7 @@ class TwoFactorController extends ClientApiController } return new JsonResponse([ - 'data' => [ - 'image_url_data' => $this->setupService->handle($request->user()), - ], + 'data' => $this->setupService->handle($request->user()), ]); } diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index e3649634f..be234d467 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -49,7 +49,7 @@ class TwoFactorSetupService * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(User $user): string + public function handle(User $user): array { $secret = ''; try { @@ -66,11 +66,14 @@ class TwoFactorSetupService $company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name'))); - return sprintf( - 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', - rawurlencode($company), - rawurlencode($user->email), - rawurlencode($secret) - ); + return [ + 'image_url_data' => sprintf( + 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', + rawurlencode($company), + rawurlencode($user->email), + rawurlencode($secret), + ), + 'secret' => $secret, + ]; } } diff --git a/resources/scripts/api/account/getTwoFactorTokenData.ts b/resources/scripts/api/account/getTwoFactorTokenData.ts new file mode 100644 index 000000000..b3669d2d6 --- /dev/null +++ b/resources/scripts/api/account/getTwoFactorTokenData.ts @@ -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 => { + return new Promise((resolve, reject) => { + http.get('/api/client/account/two-factor') + .then(({ data }) => resolve(data.data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/getTwoFactorTokenUrl.ts b/resources/scripts/api/account/getTwoFactorTokenUrl.ts deleted file mode 100644 index 6d9a2aa94..000000000 --- a/resources/scripts/api/account/getTwoFactorTokenUrl.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; - -export default (): Promise => { - return new Promise((resolve, reject) => { - http.get('/api/client/account/two-factor') - .then(({ data }) => resolve(data.data.image_url_data)) - .catch(reject); - }); -}; diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 727094782..4bb73f368 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { Form, Formik, FormikHelpers } from 'formik'; 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 { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -12,13 +12,14 @@ import Button from '@/components/elements/Button'; import asModal from '@/hoc/asModal'; import ModalContext from '@/context/ModalContext'; import QRCode from 'qrcode.react'; +import CopyOnClick from '@/components/elements/CopyOnClick'; interface Values { code: string; } const SetupTwoFactorModal = () => { - const [ token, setToken ] = useState(''); + const [ token, setToken ] = useState(null); const [ recoveryTokens, setRecoveryTokens ] = useState([]); const { dismiss, setPropOverrides } = useContext(ModalContext); @@ -26,7 +27,7 @@ const SetupTwoFactorModal = () => { const { clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { - getTwoFactorTokenUrl() + getTwoFactorTokenData() .then(setToken) .catch(error => { console.error(error); @@ -102,13 +103,17 @@ const SetupTwoFactorModal = () => {
- {!token || !token.length ? + {!token ? : - + }
@@ -121,11 +126,21 @@ const SetupTwoFactorModal = () => { title={'Code From Authenticator'} description={'Enter the code from your authenticator device after scanning the QR image.'} /> + {token && +
+ Alternatively, enter the following token into your authenticator application: + +
+ + {token.secret} + +
+
+
+ }
- +